policy: support reading policy from stdin via --file -

Allow passing policy content through stdin by specifying
"--file -" in the eval command. This enables piping policy
data without requiring a file on disk.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-03-23 11:13:46 -07:00
parent 7df7b4205b
commit 64cd4136d9
3 changed files with 77 additions and 4 deletions
+17 -3
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/fs" "io/fs"
"maps" "maps"
"os" "os"
@@ -191,9 +192,8 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
if opts.filename == "" { if opts.filename == "" {
return errors.New("filename is required") return errors.New("filename is required")
} }
policyName := opts.filename policyName, policyFile := policyFileNames(opts.filename)
policyFile := policyName + ".rego" policyData, err := readPolicyData(policyFile, os.Stdin)
policyData, err := os.ReadFile(policyFile)
if err != nil { if err != nil {
return errors.Wrapf(err, "failed to read policy file %s", policyFile) return errors.Wrapf(err, "failed to read policy file %s", policyFile)
} }
@@ -267,6 +267,20 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
} }
} }
func policyFileNames(filename string) (string, string) {
if filename == "-" {
return "stdin", filename
}
return filename, filename + ".rego"
}
func readPolicyData(filename string, stdin io.Reader) ([]byte, error) {
if filename == "-" {
return io.ReadAll(stdin)
}
return os.ReadFile(filename)
}
func selectReloadFields(fields []string, unknowns []string) ([]string, []string) { func selectReloadFields(fields []string, unknowns []string) ([]string, []string) {
if len(fields) == 0 { if len(fields) == 0 {
return nil, nil return nil, nil
+2 -1
View File
@@ -10,7 +10,8 @@ Evaluate policy for a source
| `--builder` | `string` | | Override the configured builder instance | | `--builder` | `string` | | Override the configured builder instance |
| `-D`, `--debug` | `bool` | | Enable debug logging | | `-D`, `--debug` | `bool` | | Enable debug logging |
| `--fields` | `stringSlice` | | Fields to evaluate | | `--fields` | `stringSlice` | | Fields to evaluate |
| `--filename` | `string` | `Dockerfile` | Policy filename to evaluate | | `-f`, `--file` | `string` | `Dockerfile` | Policy filename to evaluate |
| `--platform` | `string` | | Target platform for policy evaluation |
| `--print` | `bool` | | Print policy output | | `--print` | `bool` | | Print policy output |
+58
View File
@@ -21,6 +21,7 @@ import (
var policyEvalTests = []func(t *testing.T, sb integration.Sandbox){ var policyEvalTests = []func(t *testing.T, sb integration.Sandbox){
testPolicyEvalAllow, testPolicyEvalAllow,
testPolicyEvalDeny, testPolicyEvalDeny,
testPolicyEvalStdinFile,
testPolicyEvalPrint, testPolicyEvalPrint,
testPolicyEvalFields, testPolicyEvalFields,
testPolicyEvalLabel, testPolicyEvalLabel,
@@ -88,6 +89,63 @@ decision := {"allow": allow}
require.Contains(t, string(out), "policy denied") require.Contains(t, string(out), "policy denied")
} }
func testPolicyEvalStdinFile(t *testing.T, sb integration.Sandbox) {
skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+")
testCases := []struct {
name string
policy string
wantErrContains string
}{
{
name: "allow",
policy: `
package docker
default allow = false
allow if not input.image
allow if input.image.repo == "busybox"
decision := {"allow": allow}
`,
},
{
name: "deny",
policy: `
package docker
default allow = false
allow if input.image.repo == "alpine"
decision := {"allow": allow}
`,
wantErrContains: "policy denied",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cmd := buildxCmd(sb, withArgs(
"policy",
"eval",
"--file",
"-",
"docker-image://busybox:latest",
))
cmd.Stdin = strings.NewReader(tc.policy)
out, err := cmd.CombinedOutput()
if tc.wantErrContains == "" {
require.NoError(t, err, string(out))
return
}
require.Error(t, err, string(out))
require.Contains(t, string(out), tc.wantErrContains)
})
}
}
func testPolicyEvalPrint(t *testing.T, sb integration.Sandbox) { func testPolicyEvalPrint(t *testing.T, sb integration.Sandbox) {
skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+") skipNoCompatBuildKit(t, sb, ">= 0.26.0-0", "policy input requires BuildKit v0.26.0+")
cmd := buildxCmd(sb, withArgs( cmd := buildxCmd(sb, withArgs(