Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/exec: adds --stdin-input (-I) flag for input piping or manual entry #6822

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit][ We could use t.Cleanup() here. But it doesn't make a difference, I believe.

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