Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implementation of the capture_local() Starlark function
This patch implements the Go code for starlark builtin function capture_local(). This function allows Crashd script to execute a commands on the local machine and capture the result in a local file. This patch does the followings: - Adds Go function to support starlark builtin func for capture_local - Adds and updates tests for capture_local Signed-off-by: Vladimir Vivien <vivienv@vmware.com>
- Loading branch information
1 parent
98f378f
commit f108963
Showing
5 changed files
with
288 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// Copyright (c) 2020 VMware, Inc. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package starlark | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/vladimirvivien/echo" | ||
"go.starlark.net/starlark" | ||
) | ||
|
||
// captureLocalFunc is a built-in starlark function that runs a provided command on the local machine. | ||
// The output of the command is stored in a file at a specified location under the workdir directory. | ||
// Starlark format: run_local(cmd=<command> [,workdir=path][,file_name=name][,desc=description]) | ||
func captureLocalFunc(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { | ||
var cmdStr, workdir, fileName, desc string | ||
if err := starlark.UnpackArgs( | ||
identifiers.captureLocal, args, kwargs, | ||
"cmd", &cmdStr, | ||
"workdir?", &workdir, | ||
"file_name?", &fileName, | ||
"desc?", &desc, | ||
); err != nil { | ||
return starlark.None, err | ||
} | ||
|
||
if len(workdir) == 0 { | ||
dir, err := getWorkdirFromThread(thread) | ||
if err != nil { | ||
return starlark.None, err | ||
} | ||
workdir = dir | ||
} | ||
if len(fileName) == 0 { | ||
fileName = fmt.Sprintf("%s.txt", sanitizeStr(cmdStr)) | ||
} | ||
|
||
filePath := filepath.Join(workdir, fileName) | ||
if err := os.MkdirAll(workdir, 0744); err != nil && !os.IsExist(err) { | ||
return starlark.None, fmt.Errorf("%s: %s", identifiers.captureLocal, err) | ||
} | ||
|
||
p := echo.New().RunProc(cmdStr) | ||
if p.Err() != nil { | ||
return starlark.None, fmt.Errorf("%s: %s", identifiers.captureLocal, p.Err()) | ||
} | ||
|
||
if err := captureOutput(p.Out(), filePath, desc); err != nil { | ||
return starlark.None, fmt.Errorf("%s: %s", identifiers.captureLocal, err) | ||
} | ||
|
||
return starlark.String(filePath), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
// Copyright (c) 2020 VMware, Inc. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package starlark | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"go.starlark.net/starlark" | ||
) | ||
|
||
func TestCaptureLocalFunc(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
args func(t *testing.T) []starlark.Tuple | ||
eval func(t *testing.T, kwargs []starlark.Tuple) | ||
}{ | ||
{ | ||
name: "capture with defaults", | ||
args: func(t *testing.T) []starlark.Tuple { | ||
return []starlark.Tuple{{starlark.String("cmd"), starlark.String("echo 'Hello World!'")}} | ||
}, | ||
eval: func(t *testing.T, kwargs []starlark.Tuple) { | ||
val, err := captureLocalFunc(newTestThreadLocal(t), nil, nil, kwargs) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
expected := filepath.Join(defaults.workdir, fmt.Sprintf("%s.txt", sanitizeStr("echo 'Hello World!'"))) | ||
result := "" | ||
if r, ok := val.(starlark.String); ok { | ||
result = string(r) | ||
} | ||
defer func() { | ||
os.RemoveAll(result) | ||
os.RemoveAll(defaults.workdir) | ||
}() | ||
|
||
if result != expected { | ||
t.Errorf("unexpected result: %s", result) | ||
} | ||
|
||
file, err := os.Open(result) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
buf := new(bytes.Buffer) | ||
if _, err := io.Copy(buf, file); err != nil { | ||
t.Fatal(err) | ||
} | ||
expected = strings.TrimSpace(buf.String()) | ||
if expected != "Hello World!" { | ||
t.Errorf("unexpected content captured: %s", expected) | ||
} | ||
if err := file.Close(); err != nil { | ||
t.Error(err) | ||
} | ||
}, | ||
}, | ||
{ | ||
name: "capture with args", | ||
args: func(t *testing.T) []starlark.Tuple { | ||
return []starlark.Tuple{ | ||
{starlark.String("cmd"), starlark.String("echo 'Hello World!'")}, | ||
{starlark.String("workdir"), starlark.String("/tmp/capturecrashd")}, | ||
{starlark.String("file_name"), starlark.String("echo.txt")}, | ||
{starlark.String("desc"), starlark.String("echo command")}, | ||
} | ||
}, | ||
eval: func(t *testing.T, kwargs []starlark.Tuple) { | ||
val, err := captureLocalFunc(newTestThreadLocal(t), nil, nil, kwargs) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
expected := filepath.Join("/tmp/capturecrashd", "echo.txt") | ||
result := "" | ||
if r, ok := val.(starlark.String); ok { | ||
result = string(r) | ||
} | ||
defer func() { | ||
os.RemoveAll(result) | ||
os.RemoveAll(defaults.workdir) | ||
}() | ||
|
||
if result != expected { | ||
t.Errorf("unexpected result: %s", result) | ||
} | ||
|
||
file, err := os.Open(result) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
buf := new(bytes.Buffer) | ||
if _, err := io.Copy(buf, file); err != nil { | ||
t.Fatal(err) | ||
} | ||
expected = strings.TrimSpace(buf.String()) | ||
if expected != "echo command\nHello World!" { | ||
t.Errorf("unexpected content captured: %s", expected) | ||
} | ||
if err := file.Close(); err != nil { | ||
t.Error(err) | ||
} | ||
}, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
test.eval(t, test.args(t)) | ||
}) | ||
} | ||
} | ||
|
||
func TestCaptureLocalScript(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
script string | ||
eval func(t *testing.T, script string) | ||
}{ | ||
{ | ||
name: "capture local defaults", | ||
script: ` | ||
result = capture_local("echo 'Hello World!'") | ||
`, | ||
eval: func(t *testing.T, script string) { | ||
exe := New() | ||
if err := exe.Exec("test.star", strings.NewReader(script)); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
expected := filepath.Join(defaults.workdir, fmt.Sprintf("%s.txt", sanitizeStr("echo 'Hello World!'"))) | ||
var result string | ||
resultVal := exe.result["result"] | ||
if resultVal == nil { | ||
t.Fatal("capture_local() should be assigned to a variable for test") | ||
} | ||
res, ok := resultVal.(starlark.String) | ||
if !ok { | ||
t.Fatal("capture_local() should return a string") | ||
} | ||
result = string(res) | ||
defer func() { | ||
os.RemoveAll(result) | ||
os.RemoveAll(defaults.workdir) | ||
}() | ||
|
||
if result != expected { | ||
t.Errorf("unexpected result: %s", result) | ||
} | ||
|
||
file, err := os.Open(result) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
buf := new(bytes.Buffer) | ||
if _, err := io.Copy(buf, file); err != nil { | ||
t.Fatal(err) | ||
} | ||
expected = strings.TrimSpace(buf.String()) | ||
if expected != "Hello World!" { | ||
t.Errorf("unexpected content captured: %s", expected) | ||
} | ||
if err := file.Close(); err != nil { | ||
t.Error(err) | ||
} | ||
}, | ||
}, | ||
{ | ||
name: "capture local with args", | ||
script: ` | ||
result = capture_local(cmd="echo 'Hello World!'", workdir="/tmp/capturecrash", file_name="echo_out.txt", desc="output command") | ||
`, | ||
eval: func(t *testing.T, script string) { | ||
exe := New() | ||
if err := exe.Exec("test.star", strings.NewReader(script)); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
expected := filepath.Join("/tmp/capturecrash", "echo_out.txt") | ||
var result string | ||
resultVal := exe.result["result"] | ||
if resultVal == nil { | ||
t.Fatal("capture_local() should be assigned to a variable for test") | ||
} | ||
res, ok := resultVal.(starlark.String) | ||
if !ok { | ||
t.Fatal("capture_local() should return a string") | ||
} | ||
result = string(res) | ||
defer func() { | ||
os.RemoveAll(result) | ||
os.RemoveAll(defaults.workdir) | ||
}() | ||
|
||
if result != expected { | ||
t.Errorf("unexpected result: %s", result) | ||
} | ||
|
||
file, err := os.Open(result) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
buf := new(bytes.Buffer) | ||
if _, err := io.Copy(buf, file); err != nil { | ||
t.Fatal(err) | ||
} | ||
expected = strings.TrimSpace(buf.String()) | ||
if expected != "output command\nHello World!" { | ||
t.Errorf("unexpected content captured: %s", expected) | ||
} | ||
if err := file.Close(); err != nil { | ||
t.Error(err) | ||
} | ||
}, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
test.eval(t, test.script) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters