Skip to content

Commit

Permalink
cmd/exec: adds --stdin-input (-I) flag for input piping or manual ent…
Browse files Browse the repository at this point in the history
…ry (#6822)

Signed-off-by: Colin Lacy <[email protected]>
  • Loading branch information
colinjlacy authored Jun 26, 2024
1 parent 96ecf38 commit 96800d7
Show file tree
Hide file tree
Showing 13 changed files with 915 additions and 179 deletions.
14 changes: 13 additions & 1 deletion cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -52,7 +53,6 @@ the OPA configuration) against each input file. This can be overridden by
specifying the --decision argument and pointing at a specific policy decision,
e.g., opa exec --decision /foo/bar/baz ...`,

Args: cobra.MinimumNArgs(1),
PreRunE: func(cmd *cobra.Command, _ []string) error {
return env.CmdFlags.CheckEnvironmentVariables(cmd)
},
Expand All @@ -78,6 +78,7 @@ e.g., opa exec --decision /foo/bar/baz ...`,
cmd.Flags().VarP(params.LogLevel, "log-level", "l", "set log level")
cmd.Flags().Var(params.LogFormat, "log-format", "set log format")
cmd.Flags().StringVar(&params.LogTimestampFormat, "log-timestamp-format", "", "set log timestamp format (OPA_LOG_TIMESTAMP_FORMAT environment variable)")
cmd.Flags().BoolVarP(&params.StdIn, "stdin-input", "I", false, "read input document from stdin rather than a static file")
cmd.Flags().DurationVar(&params.Timeout, "timeout", 0, "set exec timeout with a Go-style duration, such as '5m 30s'. (default unlimited)")
addV1CompatibleFlag(cmd.Flags(), &params.V1Compatible, false)

Expand All @@ -95,6 +96,10 @@ func runExec(params *exec.Params) error {
}

func runExecWithContext(ctx context.Context, params *exec.Params) error {
if minimumInputErr := validateMinimumInput(params); minimumInputErr != nil {
return minimumInputErr
}

stdLogger, consoleLogger, err := setupLogging(params.LogLevel.String(), params.LogFormat.String(), params.LogTimestampFormat)
if err != nil {
return fmt.Errorf("config error: %w", err)
Expand Down Expand Up @@ -258,3 +263,10 @@ func injectExplicitBundles(root map[string]interface{}, paths []string) error {

return nil
}

func validateMinimumInput(params *exec.Params) error {
if !params.StdIn && len(params.Paths) == 0 {
return errors.New("requires at least 1 path arg, or the --stdin-input flag")
}
return nil
}
154 changes: 132 additions & 22 deletions cmd/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -771,14 +771,14 @@ func TestFailFlagCases(t *testing.T) {
"files/test.json": `{"foo": 7}`,
"bundle/x.rego": `package fail.defined.flag
some_function {
input.foo == 7
}
default fail_test := false
fail_test {
some_function
}`,
some_function {
input.foo == 7
}
default fail_test := false
fail_test {
some_function
}`,
},
decision: "fail/defined/flag/fail_test",
expectError: true,
Expand Down Expand Up @@ -853,14 +853,14 @@ func TestFailFlagCases(t *testing.T) {
"files/test.json": `{"foo": 7}`,
"bundle/x.rego": `package fail.defined.flag
some_function {
input.foo == 7
}
some_function {
input.foo == 7
}
default fail_test := false
fail_test {
some_function
}`,
default fail_test := false
fail_test {
some_function
}`,
},
decision: "fail/defined/flag/fail_test",
expectError: false,
Expand Down Expand Up @@ -936,14 +936,14 @@ func TestFailFlagCases(t *testing.T) {
"files/test.json": `{"foo": 7}`,
"bundle/x.rego": `package fail.non.empty.flag
some_function {
input.foo == 7
}
some_function {
input.foo == 7
}
default fail_test := false
fail_test {
some_function
}`,
default fail_test := false
fail_test {
some_function
}`,
},
decision: "fail/non/empty/flag/fail_test",
expectError: true,
Expand Down Expand Up @@ -1045,6 +1045,116 @@ func TestFailFlagCases(t *testing.T) {
}
}

func TestExecWithInvalidInputOptions(t *testing.T) {
tests := []struct {
description string
files map[string]string
stdIn bool
input string
expectError bool
expected string
}{
{
description: "path passed in as arg should not raise error",
files: map[string]string{
"files/test.json": `{"foo": 7}`,
"bundle/x.rego": `package system
test_fun := x {
x = false
x
}
undefined_test {
test_fun
}`,
},
expectError: false,
expected: "",
},
{
description: "no paths passed in as args should raise error if --stdin-input flag not set",
files: map[string]string{
"bundle/x.rego": `package system
test_fun := x {
x = false
x
}
undefined_test {
test_fun
}`,
},
expectError: true,
expected: "requires at least 1 path arg, or the --stdin-input flag",
},
{
description: "should not raise error if --stdin-input flag is set when no paths passed in as args",
files: map[string]string{
"bundle/x.rego": `package system
test_fun := x {
x = false
x
}
undefined_test {
test_fun
}`,
},
stdIn: true,
input: `{"foo": 7}`,
expectError: false,
expected: "",
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
test.WithTempFS(tt.files, func(dir string) {
var buf bytes.Buffer
params := exec.NewParams(&buf)
_ = params.OutputFormat.Set("json")
params.BundlePaths = []string{dir + "/bundle/"}
if tt.stdIn {
params.StdIn = true
tempFile, err := os.CreateTemp("", "test")
if err != nil {
t.Fatalf("unexpected error creating temp file: %q", err.Error())
}
if _, err := tempFile.Write([]byte(tt.input)); err != nil {
t.Fatalf("unexpeced error when writing to temp file: %q", err.Error())
}
if _, err := tempFile.Seek(0, 0); err != nil {
t.Fatalf("unexpected error when rewinding temp file: %q", err.Error())
}
oldStdin := os.Stdin
defer func() {
os.Stdin = oldStdin
os.Remove(tempFile.Name())
}()
os.Stdin = tempFile
} else {
if _, ok := tt.files["files/test.json"]; ok {
params.Paths = append(params.Paths, dir+"/files/")
}
}

err := runExec(params)
if err != nil && !tt.expectError {
t.Fatalf("unexpected error in test: %q", err.Error())
}
if err == nil && tt.expectError {
t.Fatalf("expected error %q, but none occurred in test", tt.expected)
}
if err != nil && err.Error() != tt.expected {
t.Fatalf("expected error %q, but got %q", tt.expected, err.Error())
}
})
})
}
}

func TestExecTimeoutWithMalformedRemoteBundle(t *testing.T) {
test.WithTempFS(map[string]string{}, func(dir string) {
// Note(philipc): We add the "raw bundles" flag so that we can stuff a
Expand Down
Loading

0 comments on commit 96800d7

Please sign in to comment.