diff --git a/pkg/executor/dry.go b/pkg/executor/dry.go index 10bc4949..ee912d98 100644 --- a/pkg/executor/dry.go +++ b/pkg/executor/dry.go @@ -29,9 +29,9 @@ func (ex *Dry) SetSecrets(secrets []string) { } // Run shows the command content, doesn't execute it -func (ex *Dry) Run(_ context.Context, cmd string, verbose bool) (out []string, err error) { +func (ex *Dry) Run(_ context.Context, cmd string, opts *RunOpts) (out []string, err error) { log.Printf("[DEBUG] run %s", cmd) - outLog, _ := MakeOutAndErrWriters(ex.hostAddr, ex.hostName, verbose, ex.secrets) + outLog, _ := MakeOutAndErrWriters(ex.hostAddr, ex.hostName, opts != nil && opts.Verbose, ex.secrets) var stdoutBuf bytes.Buffer mwr := io.MultiWriter(outLog, &stdoutBuf) mwr.Write([]byte(cmd)) //nolint @@ -44,7 +44,8 @@ func (ex *Dry) Run(_ context.Context, cmd string, verbose bool) (out []string, e } // Upload doesn't actually upload, just prints the command -func (ex *Dry) Upload(_ context.Context, local, remote string, mkdir bool) (err error) { +func (ex *Dry) Upload(_ context.Context, local, remote string, opts *UpDownOpts) (err error) { + mkdir := opts != nil && opts.Mkdir log.Printf("[DEBUG] upload %s to %s, mkdir: %v", local, remote, mkdir) if strings.Contains(remote, "spot-script") { // this is a temp script created by spot to perform script execution on remote host @@ -69,19 +70,26 @@ func (ex *Dry) Upload(_ context.Context, local, remote string, mkdir bool) (err } // Download file from remote server with scp -func (ex *Dry) Download(_ context.Context, remote, local string, mkdir bool) (err error) { +func (ex *Dry) Download(_ context.Context, remote, local string, opts *UpDownOpts) (err error) { + mkdir := opts != nil && opts.Mkdir log.Printf("[DEBUG] download %s to %s, mkdir: %v", local, remote, mkdir) return nil } // Sync doesn't sync anything, just prints the command -func (ex *Dry) Sync(_ context.Context, localDir, remoteDir string, del bool, exclude []string) ([]string, error) { - log.Printf("[DEBUG] sync %s to %s, delite: %v, exlcude: %v", localDir, remoteDir, del, exclude) //nolint +func (ex *Dry) Sync(_ context.Context, localDir, remoteDir string, opts *SyncOpts) ([]string, error) { + del := opts != nil && opts.Delete + exclude := []string{} + if opts != nil { + exclude = opts.Exclude + } + log.Printf("[DEBUG] sync %s to %s, delete: %v, exlcude: %v", localDir, remoteDir, del, exclude) //nolint return nil, nil } // Delete doesn't delete anything, just prints the command -func (ex *Dry) Delete(_ context.Context, remoteFile string, recursive bool) (err error) { +func (ex *Dry) Delete(_ context.Context, remoteFile string, opts *DeleteOpts) (err error) { + recursive := opts != nil && opts.Recursive log.Printf("[DEBUG] delete %s, recursive: %v", remoteFile, recursive) return nil } diff --git a/pkg/executor/dry_test.go b/pkg/executor/dry_test.go index 765ad83b..6f5da714 100644 --- a/pkg/executor/dry_test.go +++ b/pkg/executor/dry_test.go @@ -15,7 +15,7 @@ import ( func TestDry_Run(t *testing.T) { ctx := context.Background() dry := NewDry("hostAddr", "hostName") - res, err := dry.Run(ctx, "ls -la /srv", true) + res, err := dry.Run(ctx, "ls -la /srv", &RunOpts{Verbose: true}) require.NoError(t, err) require.Len(t, res, 1) require.Equal(t, "ls -la /srv", res[0]) @@ -37,7 +37,7 @@ func TestDryUpload(t *testing.T) { } stdout := captureOutput(func() { - err = dry.Upload(context.Background(), tempFile.Name(), "remote/path/spot-script", true) + err = dry.Upload(context.Background(), tempFile.Name(), "remote/path/spot-script", &UpDownOpts{Mkdir: true}) }) require.NoError(t, err) @@ -58,7 +58,7 @@ func TestDryUpload_FileOpenError(t *testing.T) { hostName: "host1", } - err := dry.Upload(context.Background(), nonExistentFile, "remote/path/spot-script", true) + err := dry.Upload(context.Background(), nonExistentFile, "remote/path/spot-script", &UpDownOpts{Mkdir: true}) require.Error(t, err) assert.Contains(t, err.Error(), "open non_existent_file", "expected error message containing 'open non_existent_file' not found") } @@ -77,22 +77,22 @@ func TestDryOperations(t *testing.T) { { name: "download", operation: func() error { - return dry.Download(context.Background(), "remote/path", "local/path", true) + return dry.Download(context.Background(), "remote/path", "local/path", &UpDownOpts{Mkdir: true}) }, expectedLog: "[DEBUG] download local/path to remote/path, mkdir: true", }, { name: "sync", operation: func() error { - _, err := dry.Sync(context.Background(), "local/dir", "remote/dir", true, nil) + _, err := dry.Sync(context.Background(), "local/dir", "remote/dir", &SyncOpts{Delete: true}) return err }, - expectedLog: "[DEBUG] sync local/dir to remote/dir, delite: true", + expectedLog: "[DEBUG] sync local/dir to remote/dir, delete: true", }, { name: "delete", operation: func() error { - return dry.Delete(context.Background(), "remote/file", true) + return dry.Delete(context.Background(), "remote/file", &DeleteOpts{Recursive: true}) }, expectedLog: "[DEBUG] delete remote/file, recursive: true", }, diff --git a/pkg/executor/executor.go b/pkg/executor/executor.go index 1c202f88..e9db6652 100644 --- a/pkg/executor/executor.go +++ b/pkg/executor/executor.go @@ -21,14 +21,39 @@ import ( // Implemented by Remote and Local structs. type Interface interface { SetSecrets(secrets []string) - Run(ctx context.Context, c string, verbose bool) (out []string, err error) - Upload(ctx context.Context, local, remote string, mkdir bool) (err error) - Download(ctx context.Context, remote, local string, mkdir bool) (err error) - Sync(ctx context.Context, localDir, remoteDir string, del bool, exclude []string) ([]string, error) - Delete(ctx context.Context, remoteFile string, recursive bool) (err error) + Run(ctx context.Context, c string, opts *RunOpts) (out []string, err error) + Upload(ctx context.Context, local, remote string, opts *UpDownOpts) (err error) + Download(ctx context.Context, remote, local string, opts *UpDownOpts) (err error) + Sync(ctx context.Context, localDir, remoteDir string, opts *SyncOpts) ([]string, error) + Delete(ctx context.Context, remoteFile string, opts *DeleteOpts) (err error) Close() error } +// RunOpts is a struct for run options. +type RunOpts struct { + Verbose bool // print more info to primary stdout +} + +// UpDownOpts is a struct for upload and download options. +type UpDownOpts struct { + Mkdir bool // create remote directory if it does not exist + Checksum bool // compare checksums of local and remote files, default is size and modtime + Force bool // overwrite existing files on remote +} + +// SyncOpts is a struct for sync options. +type SyncOpts struct { + Delete bool // delete extra files on remote + Exclude []string // exclude files matching the given patterns + Checksum bool // compare checksums of local and remote files, default is size and modtime + Force bool // overwrite existing files on remote +} + +// DeleteOpts is a struct for delete options. +type DeleteOpts struct { + Recursive bool // delete directories recursively +} + // StdOutLogWriter is a writer that writes to log with a prefix and a log level. type StdOutLogWriter struct { prefix string diff --git a/pkg/executor/local.go b/pkg/executor/local.go index 9d55fa53..e25e6514 100644 --- a/pkg/executor/local.go +++ b/pkg/executor/local.go @@ -25,10 +25,10 @@ func (l *Local) SetSecrets(secrets []string) { } // Run executes command on local hostAddr, inside the shell -func (l *Local) Run(ctx context.Context, cmd string, verbose bool) (out []string, err error) { +func (l *Local) Run(ctx context.Context, cmd string, opts *RunOpts) (out []string, err error) { command := exec.CommandContext(ctx, "sh", "-c", cmd) - outLog, errLog := MakeOutAndErrWriters("localhost", "", verbose, l.secrets) + outLog, errLog := MakeOutAndErrWriters("localhost", "", opts != nil && opts.Verbose, l.secrets) outLog.Write([]byte(cmd)) //nolint var stdoutBuf bytes.Buffer @@ -47,7 +47,7 @@ func (l *Local) Run(ctx context.Context, cmd string, verbose bool) (out []string } // Upload just copy file from one place to another -func (l *Local) Upload(_ context.Context, src, dst string, mkdir bool) (err error) { +func (l *Local) Upload(_ context.Context, src, dst string, opts *UpDownOpts) (err error) { // check if the local parameter contains a glob pattern matches, err := filepath.Glob(src) @@ -59,7 +59,7 @@ func (l *Local) Upload(_ context.Context, src, dst string, mkdir bool) (err erro return fmt.Errorf("source file %q not found", src) } - if mkdir { + if opts != nil && opts.Mkdir { if err = os.MkdirAll(filepath.Dir(dst), 0o750); err != nil { return fmt.Errorf("can't create local dir %s: %w", filepath.Dir(dst), err) } @@ -78,18 +78,22 @@ func (l *Local) Upload(_ context.Context, src, dst string, mkdir bool) (err erro } // Download just copy file from one place to another -func (l *Local) Download(_ context.Context, src, dst string, mkdir bool) (err error) { - return l.Upload(context.Background(), src, dst, mkdir) // same as upload for local +func (l *Local) Download(_ context.Context, src, dst string, opts *UpDownOpts) (err error) { + return l.Upload(context.Background(), src, dst, opts) // same as upload for local } // Sync directories from src to dst -func (l *Local) Sync(ctx context.Context, src, dst string, del bool, excl []string) ([]string, error) { +func (l *Local) Sync(ctx context.Context, src, dst string, opts *SyncOpts) ([]string, error) { + excl := []string{} + if opts != nil { + excl = opts.Exclude + } copiedFiles, err := l.syncSrcToDst(ctx, src, dst, excl) if err != nil { return nil, err } - if del { + if opts != nil && opts.Delete { if err := l.removeExtraDstFiles(ctx, src, dst); err != nil { return nil, err } @@ -99,7 +103,8 @@ func (l *Local) Sync(ctx context.Context, src, dst string, del bool, excl []stri } // Delete file or directory -func (l *Local) Delete(_ context.Context, remoteFile string, recursive bool) (err error) { +func (l *Local) Delete(_ context.Context, remoteFile string, opts *DeleteOpts) (err error) { + recursive := opts != nil && opts.Recursive if !recursive { return os.Remove(remoteFile) } diff --git a/pkg/executor/local_test.go b/pkg/executor/local_test.go index 0d0287b4..bd18a532 100644 --- a/pkg/executor/local_test.go +++ b/pkg/executor/local_test.go @@ -19,26 +19,26 @@ func TestRun(t *testing.T) { l := &Local{} t.Run("single line out success", func(t *testing.T) { - out, e := l.Run(ctx, "echo 'hello world'", false) + out, e := l.Run(ctx, "echo 'hello world'", &RunOpts{Verbose: true}) require.NoError(t, e) assert.Equal(t, []string{"hello world"}, out) }) t.Run("single line out fail", func(t *testing.T) { - _, e := l.Run(ctx, "nonexistent-command", false) + _, e := l.Run(ctx, "nonexistent-command", nil) require.Error(t, e) }) t.Run("multi line out success", func(t *testing.T) { // Prepare the test environment - _, err := l.Run(ctx, "mkdir -p /tmp/st", true) + _, err := l.Run(ctx, "mkdir -p /tmp/st", &RunOpts{Verbose: true}) require.NoError(t, err) - _, err = l.Run(ctx, "cp testdata/data1.txt /tmp/st/data1.txt", true) + _, err = l.Run(ctx, "cp testdata/data1.txt /tmp/st/data1.txt", &RunOpts{Verbose: true}) require.NoError(t, err) - _, err = l.Run(ctx, "cp testdata/data2.txt /tmp/st/data2.txt", true) + _, err = l.Run(ctx, "cp testdata/data2.txt /tmp/st/data2.txt", &RunOpts{Verbose: true}) require.NoError(t, err) - out, err := l.Run(ctx, "ls -1 /tmp/st", false) + out, err := l.Run(ctx, "ls -1 /tmp/st", nil) require.NoError(t, err) assert.Equal(t, 2, len(out)) assert.Equal(t, "data1.txt", out[0]) @@ -46,12 +46,12 @@ func TestRun(t *testing.T) { }) t.Run("multi line out fail", func(t *testing.T) { - _, err := l.Run(ctx, "nonexistent-command", false) + _, err := l.Run(ctx, "nonexistent-command", nil) require.Error(t, err) }) t.Run("find out", func(t *testing.T) { - out, e := l.Run(ctx, "find /tmp/st -type f", true) + out, e := l.Run(ctx, "find /tmp/st -type f", &RunOpts{Verbose: true}) require.NoError(t, e) sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) assert.Contains(t, out, "/tmp/st/data1.txt") @@ -66,7 +66,7 @@ func TestRun(t *testing.T) { // Set up the test environment l.SetSecrets([]string{"data2"}) defer l.SetSecrets(nil) - out, e := l.Run(ctx, "find /tmp/st -type f", true) + out, e := l.Run(ctx, "find /tmp/st -type f", &RunOpts{Verbose: true}) writer.Close() os.Stdout = originalStdout @@ -112,7 +112,7 @@ func TestUploadAndDownload(t *testing.T) { } // we want to test both upload and download, so we create a function type. those functions should do the same thing - type fn func(ctx context.Context, src, dst string, mkdir bool) (err error) + type fn func(ctx context.Context, src, dst string, opts *UpDownOpts) (err error) l := &Local{} fns := []struct { name string @@ -141,7 +141,7 @@ func TestUploadAndDownload(t *testing.T) { dstFile := filepath.Join(dstDir, filepath.Base(srcFile.Name())) - err = fn.fn(context.Background(), srcFile.Name(), dstFile, tc.mkdir) + err = fn.fn(context.Background(), srcFile.Name(), dstFile, &UpDownOpts{Mkdir: tc.mkdir}) if tc.expectError { assert.Error(t, err, "expected an error") @@ -176,7 +176,7 @@ func TestUploadDownloadWithGlob(t *testing.T) { require.NoError(t, err) defer os.RemoveAll(dstDir) - type fn func(ctx context.Context, src, dst string, mkdir bool) (err error) + type fn func(ctx context.Context, src, dst string, opts *UpDownOpts) (err error) l := &Local{} fns := []struct { @@ -226,8 +226,7 @@ func TestUploadDownloadWithGlob(t *testing.T) { } { for _, fn := range fns { t.Run(fmt.Sprintf("%s#%s", tc.name, fn.name), func(t *testing.T) { - err := fn.fn(context.Background(), tc.src, tc.dst, tc.mkdir) - + err := fn.fn(context.Background(), tc.src, tc.dst, &UpDownOpts{Mkdir: tc.mkdir}) if tc.expectError { assert.Error(t, err, "expected an error") return @@ -449,7 +448,7 @@ func TestLocal_Sync(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - copiedFiles, err := svc.Sync(ctx, srcDir, dstDir, tc.del, tc.exclude) + copiedFiles, err := svc.Sync(ctx, srcDir, dstDir, &SyncOpts{Delete: tc.del, Exclude: tc.exclude}) require.NoError(t, err) assert.ElementsMatch(t, tc.expected, copiedFiles) @@ -542,7 +541,7 @@ func TestDelete(t *testing.T) { } l := &Local{} - err = l.Delete(context.Background(), remoteFile, tc.recursive) + err = l.Delete(context.Background(), remoteFile, &DeleteOpts{Recursive: tc.recursive}) if tc.expectError { assert.Error(t, err, "expected an error") } else { @@ -596,7 +595,7 @@ func TestUpload_SpecialCharacterInPath(t *testing.T) { dstFile := filepath.Join(dstDir, "file_with_special_#_character.txt") - err = l.Upload(context.Background(), srcFile.Name(), dstFile, true) + err = l.Upload(context.Background(), srcFile.Name(), dstFile, &UpDownOpts{Mkdir: true}) assert.NoError(t, err, "unexpected error") dstContent, err := os.ReadFile(dstFile) diff --git a/pkg/executor/remote.go b/pkg/executor/remote.go index 9ad01f63..059df5cf 100644 --- a/pkg/executor/remote.go +++ b/pkg/executor/remote.go @@ -40,17 +40,17 @@ func (ex *Remote) SetSecrets(secrets []string) { } // Run command on remote server. -func (ex *Remote) Run(ctx context.Context, cmd string, verbose bool) (out []string, err error) { +func (ex *Remote) Run(ctx context.Context, cmd string, opts *RunOpts) (out []string, err error) { if ex.client == nil { return nil, fmt.Errorf("client is not connected") } log.Printf("[DEBUG] run %s", cmd) - return ex.sshRun(ctx, ex.client, cmd, verbose) + return ex.sshRun(ctx, ex.client, cmd, opts != nil && opts.Verbose) } // Upload file to remote server with scp -func (ex *Remote) Upload(ctx context.Context, local, remote string, mkdir bool) (err error) { +func (ex *Remote) Upload(ctx context.Context, local, remote string, opts *UpDownOpts) (err error) { if ex.client == nil { return fmt.Errorf("client is not connected") } @@ -81,7 +81,7 @@ func (ex *Remote) Upload(ctx context.Context, local, remote string, mkdir bool) client: ex.client, localFile: match, remoteFile: remoteFile, - mkdir: mkdir, + mkdir: opts != nil && opts.Mkdir, remoteHost: host, remotePort: port, } @@ -93,7 +93,7 @@ func (ex *Remote) Upload(ctx context.Context, local, remote string, mkdir bool) } // Download file from remote server with scp -func (ex *Remote) Download(ctx context.Context, remote, local string, mkdir bool) (err error) { +func (ex *Remote) Download(ctx context.Context, remote, local string, opts *UpDownOpts) (err error) { if ex.client == nil { return fmt.Errorf("client is not connected") } @@ -108,7 +108,7 @@ func (ex *Remote) Download(ctx context.Context, remote, local string, mkdir bool client: ex.client, localFile: local, remoteFile: remote, - mkdir: mkdir, + mkdir: opts != nil && opts.Mkdir, remoteHost: host, remotePort: port, } @@ -116,12 +116,16 @@ func (ex *Remote) Download(ctx context.Context, remote, local string, mkdir bool } // Sync compares local and remote files and uploads unmatched files, recursively. -func (ex *Remote) Sync(ctx context.Context, localDir, remoteDir string, del bool, excl []string) ([]string, error) { +func (ex *Remote) Sync(ctx context.Context, localDir, remoteDir string, opts *SyncOpts) ([]string, error) { localFiles, err := ex.getLocalFilesProperties(localDir) if err != nil { return nil, fmt.Errorf("failed to get local files properties for %s: %w", localDir, err) } + excl := []string{} + if opts != nil { + excl = opts.Exclude + } remoteFiles, err := ex.getRemoteFilesProperties(ctx, remoteDir, excl) if err != nil { return nil, fmt.Errorf("failed to get remote files properties for %s: %w", remoteDir, err) @@ -131,19 +135,19 @@ func (ex *Remote) Sync(ctx context.Context, localDir, remoteDir string, del bool for _, file := range unmatchedFiles { localPath := filepath.Join(localDir, file) remotePath := filepath.Join(remoteDir, file) - if err = ex.Upload(ctx, localPath, remotePath, true); err != nil { + if err = ex.Upload(ctx, localPath, remotePath, &UpDownOpts{Mkdir: true}); err != nil { return nil, fmt.Errorf("failed to upload %s to %s: %w", localPath, remotePath, err) } log.Printf("[INFO] synced %s to %s", localPath, remotePath) } - if del { + if opts != nil && opts.Delete { // delete remote files which are not in local. // if the missing file is a directory, delete it recursively. // note: this may cause attempts to remove files from already deleted directories, but it's ok, Delete is idempotent. for _, file := range deletedFiles { - recur := remoteFiles[file].IsDir - if err = ex.Delete(ctx, filepath.Join(remoteDir, file), recur); err != nil { + deleteOpts := &DeleteOpts{Recursive: remoteFiles[file].IsDir} + if err = ex.Delete(ctx, filepath.Join(remoteDir, file), deleteOpts); err != nil { return nil, fmt.Errorf("failed to delete %s: %w", file, err) } } @@ -154,7 +158,7 @@ func (ex *Remote) Sync(ctx context.Context, localDir, remoteDir string, del bool // Delete file on remote server. Recursively if recursive is true. // if a file or directory does not exist, returns nil, i.e. no error. -func (ex *Remote) Delete(ctx context.Context, remoteFile string, recursive bool) (err error) { +func (ex *Remote) Delete(ctx context.Context, remoteFile string, opts *DeleteOpts) (err error) { if ex.client == nil { return fmt.Errorf("client is not connected") } @@ -173,6 +177,7 @@ func (ex *Remote) Delete(ctx context.Context, remoteFile string, recursive bool) return fmt.Errorf("failed to stat %s: %w", remoteFile, err) } + recursive := opts != nil && opts.Recursive if fileInfo.IsDir() && recursive { walker := sftpClient.Walk(remoteFile) diff --git a/pkg/executor/remote_test.go b/pkg/executor/remote_test.go index c4fcb397..476839fe 100644 --- a/pkg/executor/remote_test.go +++ b/pkg/executor/remote_test.go @@ -28,13 +28,13 @@ func TestExecuter_UploadAndDownload(t *testing.T) { require.NoError(t, err) defer sess.Close() - err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/blah/data1.txt", true) + err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/blah/data1.txt", &UpDownOpts{Mkdir: true}) require.NoError(t, err) tmpFile, err := fileutils.TempFileName("", "data1.txt") require.NoError(t, err) defer os.RemoveAll(tmpFile) - err = sess.Download(ctx, "/tmp/blah/data1.txt", tmpFile, true) + err = sess.Download(ctx, "/tmp/blah/data1.txt", tmpFile, &UpDownOpts{Mkdir: true}) require.NoError(t, err) assert.FileExists(t, tmpFile) exp, err := os.ReadFile("testdata/data1.txt") @@ -56,14 +56,14 @@ func TestExecuter_UploadGlobAndDownload(t *testing.T) { require.NoError(t, err) defer sess.Close() - err = sess.Upload(ctx, "testdata/data*.txt", "/tmp/blah", true) + err = sess.Upload(ctx, "testdata/data*.txt", "/tmp/blah", &UpDownOpts{Mkdir: true}) require.NoError(t, err) { tmpFile, err := fileutils.TempFileName("", "data1.txt") require.NoError(t, err) defer os.RemoveAll(tmpFile) - err = sess.Download(ctx, "/tmp/blah/data1.txt", tmpFile, true) + err = sess.Download(ctx, "/tmp/blah/data1.txt", tmpFile, &UpDownOpts{Mkdir: true}) require.NoError(t, err) assert.FileExists(t, tmpFile) exp, err := os.ReadFile("testdata/data1.txt") @@ -76,7 +76,7 @@ func TestExecuter_UploadGlobAndDownload(t *testing.T) { tmpFile, err := fileutils.TempFileName("", "data2.txt") require.NoError(t, err) defer os.RemoveAll(tmpFile) - err = sess.Download(ctx, "/tmp/blah/data2.txt", tmpFile, true) + err = sess.Download(ctx, "/tmp/blah/data2.txt", tmpFile, &UpDownOpts{Mkdir: true}) require.NoError(t, err) assert.FileExists(t, tmpFile) exp, err := os.ReadFile("testdata/data2.txt") @@ -99,7 +99,7 @@ func TestExecuter_Upload_FailedSourceNotFound(t *testing.T) { require.NoError(t, err) defer sess.Close() - err = sess.Upload(ctx, "testdata/data-not-found.txt", "/tmp/blah/data.txt", true) + err = sess.Upload(ctx, "testdata/data-not-found.txt", "/tmp/blah/data.txt", &UpDownOpts{Mkdir: true}) require.EqualError(t, err, "source file \"testdata/data-not-found.txt\" not found") } @@ -115,7 +115,7 @@ func TestExecuter_Upload_FailedNoRemoteDir(t *testing.T) { require.NoError(t, err) defer sess.Close() - err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/blah/data1.txt", false) + err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/blah/data1.txt", nil) require.EqualError(t, err, "failed to create remote file: file does not exist") } @@ -131,7 +131,7 @@ func TestExecuter_Upload_CantMakeRemoteDir(t *testing.T) { require.NoError(t, err) defer sess.Close() - err = sess.Upload(ctx, "testdata/data1.txt", "/dev/blah/data1.txt", true) + err = sess.Upload(ctx, "testdata/data1.txt", "/dev/blah/data1.txt", &UpDownOpts{Mkdir: true}) require.EqualError(t, err, "failed to create remote directory: permission denied") } @@ -148,7 +148,7 @@ func TestExecuter_Upload_Canceled(t *testing.T) { defer sess.Close() cancel() - err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/blah/data1.txt", true) + err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/blah/data1.txt", &UpDownOpts{Mkdir: true}) require.EqualError(t, err, "failed to copy file: context canceled") } @@ -166,7 +166,7 @@ func TestExecuter_UploadCanceledWithoutMkdir(t *testing.T) { cancel() - err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/data1.txt", false) + err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/data1.txt", nil) require.EqualError(t, err, "failed to copy file: context canceled") } @@ -195,18 +195,18 @@ func TestExecuter_Run(t *testing.T) { defer sess.Close() t.Run("single line out", func(t *testing.T) { - out, e := sess.Run(ctx, "sh -c 'echo hello world'", false) + out, e := sess.Run(ctx, "sh -c 'echo hello world'", nil) require.NoError(t, e) assert.Equal(t, []string{"hello world"}, out) }) t.Run("multi line out", func(t *testing.T) { - err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/st/data1.txt", true) + err = sess.Upload(ctx, "testdata/data1.txt", "/tmp/st/data1.txt", &UpDownOpts{Mkdir: true}) assert.NoError(t, err) - err = sess.Upload(ctx, "testdata/data2.txt", "/tmp/st/data2.txt", true) + err = sess.Upload(ctx, "testdata/data2.txt", "/tmp/st/data2.txt", &UpDownOpts{Mkdir: true}) assert.NoError(t, err) - out, err := sess.Run(ctx, "ls -1 /tmp/st", false) + out, err := sess.Run(ctx, "ls -1 /tmp/st", nil) require.NoError(t, err) t.Logf("out: %v", out) assert.Equal(t, 2, len(out)) @@ -216,7 +216,7 @@ func TestExecuter_Run(t *testing.T) { t.Run("find out", func(t *testing.T) { cmd := fmt.Sprintf("find %s -type f -exec stat -c '%%n:%%s' {} \\;", "/tmp/") - out, e := sess.Run(ctx, cmd, true) + out, e := sess.Run(ctx, cmd, &RunOpts{Verbose: true}) require.NoError(t, e) sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) assert.Equal(t, []string{"/tmp/st/data1.txt:13", "/tmp/st/data2.txt:13"}, out) @@ -230,7 +230,7 @@ func TestExecuter_Run(t *testing.T) { sess.SetSecrets([]string{"data2"}) defer sess.SetSecrets(nil) cmd := fmt.Sprintf("find %s -type f -exec stat -c '%%n:%%s' {} \\;", "/tmp/") - out, e := sess.Run(ctx, cmd, true) + out, e := sess.Run(ctx, cmd, &RunOpts{Verbose: true}) writer.Close() os.Stdout = originalStdout @@ -246,14 +246,14 @@ func TestExecuter_Run(t *testing.T) { }) t.Run("command failed", func(t *testing.T) { - _, err := sess.Run(ctx, "sh -c 'exit 1'", false) + _, err := sess.Run(ctx, "sh -c 'exit 1'", nil) assert.ErrorContains(t, err, "failed to run command on remote server") }) t.Run("ctx canceled", func(t *testing.T) { ctxCancel, cancel := context.WithCancel(ctx) cancel() - _, err := sess.Run(ctxCancel, "sh -c 'echo hello world'", false) + _, err := sess.Run(ctxCancel, "sh -c 'echo hello world'", nil) assert.ErrorContains(t, err, "context canceled") }) @@ -271,64 +271,64 @@ func TestExecuter_Sync(t *testing.T) { defer sess.Close() t.Run("sync", func(t *testing.T) { - res, e := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest", true, nil) + res, e := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest", &SyncOpts{Delete: true}) require.NoError(t, e) sort.Slice(res, func(i, j int) bool { return res[i] < res[j] }) assert.Equal(t, []string{"d1/file11.txt", "file1.txt", "file2.txt"}, res) - out, e := sess.Run(ctx, "find /tmp/sync.dest -type f -exec stat -c '%s %n' {} \\;", true) + out, e := sess.Run(ctx, "find /tmp/sync.dest -type f -exec stat -c '%s %n' {} \\;", &RunOpts{Verbose: true}) require.NoError(t, e) sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) assert.Equal(t, []string{"17 /tmp/sync.dest/d1/file11.txt", "185 /tmp/sync.dest/file1.txt", "61 /tmp/sync.dest/file2.txt"}, out) - res, e = sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest", true, nil) + res, e = sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest", &SyncOpts{Delete: true}) require.NoError(t, e) assert.Equal(t, 0, len(res), "no files should be synced", res) }) t.Run("sync no src", func(t *testing.T) { - _, err = sess.Sync(ctx, "/tmp/no-such-place", "/tmp/sync.dest", true, nil) + _, err = sess.Sync(ctx, "/tmp/no-such-place", "/tmp/sync.dest", &SyncOpts{Delete: true}) require.EqualError(t, err, "failed to get local files properties for /tmp/no-such-place: failed to walk local directory"+ " /tmp/no-such-place: lstat /tmp/no-such-place: no such file or directory") }) t.Run("sync with empty dir on remote to delete", func(t *testing.T) { - _, e := sess.Run(ctx, "mkdir -p /tmp/sync.dest2/empty", true) + _, e := sess.Run(ctx, "mkdir -p /tmp/sync.dest2/empty", &RunOpts{Verbose: true}) require.NoError(t, e) - res, e := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest2", true, nil) + res, e := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest2", &SyncOpts{Delete: true}) require.NoError(t, e) sort.Slice(res, func(i, j int) bool { return res[i] < res[j] }) assert.Equal(t, []string{"d1/file11.txt", "file1.txt", "file2.txt"}, res) - out, e := sess.Run(ctx, "find /tmp/sync.dest2 -type f -exec stat -c '%s %n' {} \\;", true) + out, e := sess.Run(ctx, "find /tmp/sync.dest2 -type f -exec stat -c '%s %n' {} \\;", &RunOpts{Verbose: true}) require.NoError(t, e) sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) assert.Equal(t, []string{"17 /tmp/sync.dest2/d1/file11.txt", "185 /tmp/sync.dest2/file1.txt", "61 /tmp/sync.dest2/file2.txt"}, out) }) t.Run("sync with non-empty dir on remote to delete", func(t *testing.T) { - _, e := sess.Run(ctx, "mkdir -p /tmp/sync.dest3/empty", true) + _, e := sess.Run(ctx, "mkdir -p /tmp/sync.dest3/empty", &RunOpts{Verbose: true}) require.NoError(t, e) - _, e = sess.Run(ctx, "touch /tmp/sync.dest3/empty/afile1.txt", true) + _, e = sess.Run(ctx, "touch /tmp/sync.dest3/empty/afile1.txt", &RunOpts{Verbose: true}) require.NoError(t, e) - res, e := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest3", true, nil) + res, e := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest3", &SyncOpts{Delete: true}) require.NoError(t, e) sort.Slice(res, func(i, j int) bool { return res[i] < res[j] }) assert.Equal(t, []string{"d1/file11.txt", "file1.txt", "file2.txt"}, res) - out, e := sess.Run(ctx, "find /tmp/sync.dest3 -type f -exec stat -c '%s %n' {} \\;", true) + out, e := sess.Run(ctx, "find /tmp/sync.dest3 -type f -exec stat -c '%s %n' {} \\;", &RunOpts{Verbose: true}) require.NoError(t, e) sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) assert.Equal(t, []string{"17 /tmp/sync.dest3/d1/file11.txt", "185 /tmp/sync.dest3/file1.txt", "61 /tmp/sync.dest3/file2.txt"}, out) }) t.Run("sync with non-empty dir on remote to keep", func(t *testing.T) { - _, e := sess.Run(ctx, "mkdir -p /tmp/sync.dest4/empty", true) + _, e := sess.Run(ctx, "mkdir -p /tmp/sync.dest4/empty", &RunOpts{Verbose: true}) require.NoError(t, e) - _, e = sess.Run(ctx, "touch /tmp/sync.dest4/empty/afile1.txt", true) + _, e = sess.Run(ctx, "touch /tmp/sync.dest4/empty/afile1.txt", &RunOpts{Verbose: true}) require.NoError(t, e) - res, e := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest4", false, nil) + res, e := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest4", nil) require.NoError(t, e) sort.Slice(res, func(i, j int) bool { return res[i] < res[j] }) assert.Equal(t, []string{"d1/file11.txt", "file1.txt", "file2.txt"}, res) - out, e := sess.Run(ctx, "find /tmp/sync.dest4 -type f -exec stat -c '%s %n' {} \\;", true) + out, e := sess.Run(ctx, "find /tmp/sync.dest4 -type f -exec stat -c '%s %n' {} \\;", &RunOpts{Verbose: true}) require.NoError(t, e) sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) assert.Equal(t, []string{"0 /tmp/sync.dest4/empty/afile1.txt", "17 /tmp/sync.dest4/d1/file11.txt", @@ -347,52 +347,52 @@ func TestExecuter_Delete(t *testing.T) { require.NoError(t, err) defer sess.Close() - res, err := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest", true, nil) + res, err := sess.Sync(ctx, "testdata/sync", "/tmp/sync.dest", &SyncOpts{Delete: true}) require.NoError(t, err) sort.Slice(res, func(i, j int) bool { return res[i] < res[j] }) assert.Equal(t, []string{"d1/file11.txt", "file1.txt", "file2.txt"}, res) t.Run("delete file", func(t *testing.T) { - err = sess.Delete(ctx, "/tmp/sync.dest/file1.txt", false) + err = sess.Delete(ctx, "/tmp/sync.dest/file1.txt", nil) assert.NoError(t, err) - out, e := sess.Run(ctx, "ls -1 /tmp/sync.dest", false) + out, e := sess.Run(ctx, "ls -1 /tmp/sync.dest", nil) require.NoError(t, e) assert.Equal(t, []string{"d1", "file2.txt"}, out) }) t.Run("delete dir non-recursive", func(t *testing.T) { - err = sess.Delete(ctx, "/tmp/sync.dest", false) + err = sess.Delete(ctx, "/tmp/sync.dest", nil) require.Error(t, err) }) t.Run("delete dir", func(t *testing.T) { - err = sess.Delete(ctx, "/tmp/sync.dest", true) + err = sess.Delete(ctx, "/tmp/sync.dest", &DeleteOpts{Recursive: true}) assert.NoError(t, err) - out, e := sess.Run(ctx, "ls -1 /tmp/", true) + out, e := sess.Run(ctx, "ls -1 /tmp/", &RunOpts{Verbose: true}) require.NoError(t, e) assert.NotContains(t, out, "file2.txt", out) }) t.Run("delete empty dir", func(t *testing.T) { - _, err = sess.Run(ctx, "mkdir -p /tmp/sync.dest/empty", true) + _, err = sess.Run(ctx, "mkdir -p /tmp/sync.dest/empty", &RunOpts{Verbose: true}) require.NoError(t, err) - out, e := sess.Run(ctx, "ls -1 /tmp/sync.dest", true) + out, e := sess.Run(ctx, "ls -1 /tmp/sync.dest", &RunOpts{Verbose: true}) require.NoError(t, e) assert.Contains(t, out, "empty", out) - err = sess.Delete(ctx, "/tmp/sync.dest/empty", false) + err = sess.Delete(ctx, "/tmp/sync.dest/empty", nil) assert.NoError(t, err) - out, e = sess.Run(ctx, "ls -1 /tmp/sync.dest", true) + out, e = sess.Run(ctx, "ls -1 /tmp/sync.dest", &RunOpts{Verbose: true}) require.NoError(t, e) assert.NotContains(t, out, "empty", out) }) t.Run("delete no-such-file", func(t *testing.T) { - err = sess.Delete(ctx, "/tmp/sync.dest/no-such-file", false) + err = sess.Delete(ctx, "/tmp/sync.dest/no-such-file", nil) assert.NoError(t, err) }) t.Run("delete no-such-dir", func(t *testing.T) { - err = sess.Delete(ctx, "/tmp/sync.dest/no-such-dir", true) + err = sess.Delete(ctx, "/tmp/sync.dest/no-such-dir", &DeleteOpts{Recursive: true}) assert.NoError(t, err) }) } @@ -519,7 +519,7 @@ func Test_getRemoteFilesProperties(t *testing.T) { defer sess.Close() // create some test data on the remote host. - _, err = sess.Run(ctx, "mkdir -p /tmp/testdata/dir1 /tmp/testdata/dir2 && echo 'Hello' > /tmp/testdata/dir1/file1.txt && echo 'World' > /tmp/testdata/dir2/file2.txt", true) + _, err = sess.Run(ctx, "mkdir -p /tmp/testdata/dir1 /tmp/testdata/dir2 && echo 'Hello' > /tmp/testdata/dir1/file1.txt && echo 'World' > /tmp/testdata/dir2/file2.txt", &RunOpts{Verbose: true}) require.NoError(t, err) props, err := sess.getRemoteFilesProperties(ctx, "/tmp/testdata", nil) diff --git a/pkg/runner/commands.go b/pkg/runner/commands.go index cda5fa13..b33aa955 100644 --- a/pkg/runner/commands.go +++ b/pkg/runner/commands.go @@ -59,7 +59,7 @@ func (ec *execCmd) Script(ctx context.Context) (details string, vars map[string] details = fmt.Sprintf(" {script: %s, sudo: true}", c) c = fmt.Sprintf("sudo sh -c %q", c) } - out, err := ec.exec.Run(ctx, c, ec.verbose) + out, err := ec.exec.Run(ctx, c, &executor.RunOpts{Verbose: ec.verbose}) if err != nil { return details, nil, fmt.Errorf("can't run script on %s: %w", ec.hostAddr, err) } @@ -94,7 +94,7 @@ func (ec *execCmd) Copy(ctx context.Context) (details string, vars map[string]st if !ec.cmd.Options.Sudo { // if sudo is not set, we can use the original destination and upload the file directly details = fmt.Sprintf(" {copy: %s -> %s}", src, dst) - if err := ec.exec.Upload(ctx, src, dst, ec.cmd.Copy.Mkdir); err != nil { + if err := ec.exec.Upload(ctx, src, dst, &executor.UpDownOpts{Mkdir: ec.cmd.Copy.Mkdir}); err != nil { return details, nil, fmt.Errorf("can't copy file to %s: %w", ec.hostAddr, err) } return details, nil, nil @@ -104,7 +104,8 @@ func (ec *execCmd) Copy(ctx context.Context) (details string, vars map[string]st // if sudo is set, we need to upload the file to a temporary directory and move it to the final destination details = fmt.Sprintf(" {copy: %s -> %s, sudo: true}", src, dst) tmpDest := filepath.Join(tmpRemoteDir, filepath.Base(dst)) - if err := ec.exec.Upload(ctx, src, tmpDest, true); err != nil { // upload to a temporary directory with mkdir + if err := ec.exec.Upload(ctx, src, tmpDest, &executor.UpDownOpts{Mkdir: true}); err != nil { + // upload to a temporary directory with mkdir return details, nil, fmt.Errorf("can't copy file to %s: %w", ec.hostAddr, err) } @@ -113,7 +114,7 @@ func (ec *execCmd) Copy(ctx context.Context) (details string, vars map[string]st mvCmd = fmt.Sprintf("mv -f %s/* %s", tmpDest, dst) // move multiple files, if wildcard is used defer func() { // remove temporary directory we created under /tmp/.spot for multiple files - if _, err := ec.exec.Run(ctx, fmt.Sprintf("rm -rf %s", tmpDest), ec.verbose); err != nil { + if _, err := ec.exec.Run(ctx, fmt.Sprintf("rm -rf %s", tmpDest), &executor.RunOpts{Verbose: ec.verbose}); err != nil { log.Printf("[WARN] can't remove temporary directory on %s: %v", ec.hostAddr, err) } }() @@ -124,7 +125,7 @@ func (ec *execCmd) Copy(ctx context.Context) (details string, vars map[string]st } sudoMove := fmt.Sprintf("sudo %s", c) - if _, err := ec.exec.Run(ctx, sudoMove, ec.verbose); err != nil { + if _, err := ec.exec.Run(ctx, sudoMove, &executor.RunOpts{Verbose: ec.verbose}); err != nil { return details, nil, fmt.Errorf("can't move file to %s: %w", ec.hostAddr, err) } } @@ -156,7 +157,7 @@ func (ec *execCmd) Sync(ctx context.Context) (details string, vars map[string]st src := tmpl.apply(ec.cmd.Sync.Source) dst := tmpl.apply(ec.cmd.Sync.Dest) details = fmt.Sprintf(" {sync: %s -> %s}", src, dst) - if _, err := ec.exec.Sync(ctx, src, dst, ec.cmd.Sync.Delete, ec.cmd.Sync.Exclude); err != nil { + if _, err := ec.exec.Sync(ctx, src, dst, &executor.SyncOpts{Delete: ec.cmd.Sync.Delete, Exclude: ec.cmd.Sync.Exclude}); err != nil { return details, nil, fmt.Errorf("can't sync files on %s: %w", ec.hostAddr, err) } return details, nil, nil @@ -187,7 +188,7 @@ func (ec *execCmd) Delete(ctx context.Context) (details string, vars map[string] if !ec.cmd.Options.Sudo { // if sudo is not set, we can delete the file directly - if err := ec.exec.Delete(ctx, loc, ec.cmd.Delete.Recursive); err != nil { + if err := ec.exec.Delete(ctx, loc, &executor.DeleteOpts{Recursive: ec.cmd.Delete.Recursive}); err != nil { return details, nil, fmt.Errorf("can't delete files on %s: %w", ec.hostAddr, err) } details = fmt.Sprintf(" {delete: %s, recursive: %v}", loc, ec.cmd.Delete.Recursive) @@ -199,7 +200,7 @@ func (ec *execCmd) Delete(ctx context.Context) (details string, vars map[string] if ec.cmd.Delete.Recursive { cmd = fmt.Sprintf("sudo rm -rf %s", loc) } - if _, err := ec.exec.Run(ctx, cmd, ec.verbose); err != nil { + if _, err := ec.exec.Run(ctx, cmd, &executor.RunOpts{Verbose: ec.verbose}); err != nil { return details, nil, fmt.Errorf("can't delete file(s) on %s: %w", ec.hostAddr, err) } details = fmt.Sprintf(" {delete: %s, recursive: %v, sudo: true}", loc, ec.cmd.Delete.Recursive) @@ -272,7 +273,7 @@ func (ec *execCmd) Wait(ctx context.Context) (details string, vars map[string]st case <-timeoutTk.C: return details, nil, fmt.Errorf("timeout exceeded") case <-checkTk.C: - if _, err := ec.exec.Run(ctx, waitCmd, false); err == nil { + if _, err := ec.exec.Run(ctx, waitCmd, nil); err == nil { return details, nil, nil // command succeeded } } @@ -290,7 +291,7 @@ func (ec *execCmd) Echo(ctx context.Context) (details string, vars map[string]st if ec.cmd.Options.Sudo { echoCmd = fmt.Sprintf("sudo %s", echoCmd) } - out, err := ec.exec.Run(ctx, echoCmd, false) + out, err := ec.exec.Run(ctx, echoCmd, nil) if err != nil { return "", nil, fmt.Errorf("can't run echo command on %s: %w", ec.hostAddr, err) } @@ -322,7 +323,7 @@ func (ec *execCmd) checkCondition(ctx context.Context) (bool, error) { } // run the condition command - if _, err := ec.exec.Run(ctx, c, ec.verbose); err != nil { + if _, err := ec.exec.Run(ctx, c, &executor.RunOpts{Verbose: ec.verbose}); err != nil { log.Printf("[DEBUG] condition not passed on %s: %v", ec.hostAddr, err) if inverted { return true, nil // inverted condition failed, so we return true @@ -377,14 +378,14 @@ func (ec *execCmd) prepScript(ctx context.Context, s string, r io.Reader) (cmd s dst := filepath.Join(tmpRemoteDir, filepath.Base(tmp.Name())) // nolint // upload the script to the remote hostAddr - if err = ec.exec.Upload(ctx, tmp.Name(), dst, true); err != nil { + if err = ec.exec.Upload(ctx, tmp.Name(), dst, &executor.UpDownOpts{Mkdir: true}); err != nil { return "", nil, fmt.Errorf("can't upload script to %s: %w", ec.hostAddr, err) } cmd = fmt.Sprintf("sh -c %s", dst) teardown = func() error { // remove the script from the remote hostAddr, should be invoked by the caller after the command is executed - if err := ec.exec.Delete(ctx, dst, false); err != nil { + if err := ec.exec.Delete(ctx, dst, nil); err != nil { return fmt.Errorf("can't remove temporary remote script %s (%s): %w", dst, ec.hostAddr, err) } return nil diff --git a/pkg/runner/commands_test.go b/pkg/runner/commands_test.go index 1857abef..9202395b 100644 --- a/pkg/runner/commands_test.go +++ b/pkg/runner/commands_test.go @@ -146,7 +146,7 @@ func Test_execCmd(t *testing.T) { t.Run("wait done", func(t *testing.T) { time.AfterFunc(time.Second, func() { - _, _ = sess.Run(ctx, "touch /tmp/wait.done", false) + _, _ = sess.Run(ctx, "touch /tmp/wait.done", nil) }) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Wait: config.WaitInternal{ Command: "cat /tmp/wait.done", Timeout: 2 * time.Second, CheckDuration: time.Millisecond * 100}}} @@ -157,7 +157,7 @@ func Test_execCmd(t *testing.T) { t.Run("wait multiline done", func(t *testing.T) { time.AfterFunc(time.Second, func() { - _, _ = sess.Run(ctx, "touch /tmp/wait.done", false) + _, _ = sess.Run(ctx, "touch /tmp/wait.done", nil) }) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Wait: config.WaitInternal{ Command: "echo this is wait\ncat /tmp/wait.done", Timeout: 2 * time.Second, CheckDuration: time.Millisecond * 100}}} @@ -168,7 +168,7 @@ func Test_execCmd(t *testing.T) { t.Run("wait done with sudo", func(t *testing.T) { time.AfterFunc(time.Second, func() { - _, _ = sess.Run(ctx, "sudo touch /srv/wait.done", false) + _, _ = sess.Run(ctx, "sudo touch /srv/wait.done", nil) }) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Wait: config.WaitInternal{ Command: "cat /srv/wait.done", Timeout: 2 * time.Second, CheckDuration: time.Millisecond * 100}, @@ -194,7 +194,7 @@ func Test_execCmd(t *testing.T) { }) t.Run("delete a single file", func(t *testing.T) { - _, err := sess.Run(ctx, "touch /tmp/delete.me", true) + _, err := sess.Run(ctx, "touch /tmp/delete.me", &executor.RunOpts{Verbose: true}) require.NoError(t, err) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Delete: config.DeleteInternal{ Location: "/tmp/delete.me"}}} @@ -203,27 +203,27 @@ func Test_execCmd(t *testing.T) { }) t.Run("delete a multi-files", func(t *testing.T) { - _, err := sess.Run(ctx, "touch /tmp/delete1.me /tmp/delete2.me ", true) + _, err := sess.Run(ctx, "touch /tmp/delete1.me /tmp/delete2.me ", &executor.RunOpts{Verbose: true}) require.NoError(t, err) - _, err = sess.Run(ctx, "ls /tmp/delete1.me", true) + _, err = sess.Run(ctx, "ls /tmp/delete1.me", &executor.RunOpts{Verbose: true}) require.NoError(t, err) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{MDelete: []config.DeleteInternal{ {Location: "/tmp/delete1.me"}, {Location: "/tmp/delete2.me"}}}} _, _, err = ec.MDelete(ctx) require.NoError(t, err) - _, err = sess.Run(ctx, "ls /tmp/delete1.me", true) + _, err = sess.Run(ctx, "ls /tmp/delete1.me", &executor.RunOpts{Verbose: true}) require.Error(t, err) - _, err = sess.Run(ctx, "ls /tmp/delete2.me", true) + _, err = sess.Run(ctx, "ls /tmp/delete2.me", &executor.RunOpts{Verbose: true}) require.Error(t, err) }) t.Run("delete files recursive", func(t *testing.T) { var err error - _, err = sess.Run(ctx, "mkdir -p /tmp/delete-recursive", true) + _, err = sess.Run(ctx, "mkdir -p /tmp/delete-recursive", &executor.RunOpts{Verbose: true}) require.NoError(t, err) - _, err = sess.Run(ctx, "touch /tmp/delete-recursive/delete1.me", true) + _, err = sess.Run(ctx, "touch /tmp/delete-recursive/delete1.me", &executor.RunOpts{Verbose: true}) require.NoError(t, err) - _, err = sess.Run(ctx, "touch /tmp/delete-recursive/delete2.me", true) + _, err = sess.Run(ctx, "touch /tmp/delete-recursive/delete2.me", &executor.RunOpts{Verbose: true}) require.NoError(t, err) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Delete: config.DeleteInternal{ @@ -232,12 +232,12 @@ func Test_execCmd(t *testing.T) { _, _, err = ec.Delete(ctx) require.NoError(t, err) - _, err = sess.Run(ctx, "ls /tmp/delete-recursive", true) + _, err = sess.Run(ctx, "ls /tmp/delete-recursive", &executor.RunOpts{Verbose: true}) require.Error(t, err, "should not exist") }) t.Run("delete file with sudo", func(t *testing.T) { - _, err := sess.Run(ctx, "sudo touch /srv/delete.me", true) + _, err := sess.Run(ctx, "sudo touch /srv/delete.me", &executor.RunOpts{Verbose: true}) require.NoError(t, err) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Delete: config.DeleteInternal{ Location: "/srv/delete.me"}, Options: config.CmdOptions{Sudo: false}}} @@ -254,11 +254,11 @@ func Test_execCmd(t *testing.T) { t.Run("delete files recursive with sudo", func(t *testing.T) { var err error - _, err = sess.Run(ctx, "sudo mkdir -p /srv/delete-recursive", true) + _, err = sess.Run(ctx, "sudo mkdir -p /srv/delete-recursive", &executor.RunOpts{Verbose: true}) require.NoError(t, err) - _, err = sess.Run(ctx, "sudo touch /srv/delete-recursive/delete1.me", true) + _, err = sess.Run(ctx, "sudo touch /srv/delete-recursive/delete1.me", &executor.RunOpts{Verbose: true}) require.NoError(t, err) - _, err = sess.Run(ctx, "sudo touch /srv/delete-recursive/delete2.me", true) + _, err = sess.Run(ctx, "sudo touch /srv/delete-recursive/delete2.me", &executor.RunOpts{Verbose: true}) require.NoError(t, err) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Delete: config.DeleteInternal{ @@ -267,7 +267,7 @@ func Test_execCmd(t *testing.T) { _, _, err = ec.Delete(ctx) require.NoError(t, err) - _, err = sess.Run(ctx, "ls /srv/delete-recursive", true) + _, err = sess.Run(ctx, "ls /srv/delete-recursive", &executor.RunOpts{Verbose: true}) require.Error(t, err, "should not exist") }) @@ -280,7 +280,7 @@ func Test_execCmd(t *testing.T) { }) t.Run("condition true", func(t *testing.T) { - _, err := sess.Run(ctx, "sudo touch /srv/test.condition", true) + _, err := sess.Run(ctx, "sudo touch /srv/test.condition", &executor.RunOpts{Verbose: true}) require.NoError(t, err) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Condition: "ls -la /srv/test.condition", Script: "echo condition true", Name: "test"}} @@ -290,7 +290,7 @@ func Test_execCmd(t *testing.T) { }) t.Run("condition true inverted", func(t *testing.T) { - _, err := sess.Run(ctx, "sudo touch /srv/test.condition", true) + _, err := sess.Run(ctx, "sudo touch /srv/test.condition", &executor.RunOpts{Verbose: true}) require.NoError(t, err) ec := execCmd{exec: sess, tsk: &config.Task{Name: "test"}, cmd: config.Cmd{Condition: "! ls -la /srv/test.condition", Script: "echo condition true", Name: "test"}}