Skip to content

Commit a60388c

Browse files
feat: support print statement in gator (#2949) (#3872)
Signed-off-by: Julian van den Berkmortel <7153670+Serializator@users.noreply.github.com> Co-authored-by: Jaydip Gabani <gabanijaydip@gmail.com>
1 parent 3631fc7 commit a60388c

File tree

12 files changed

+266
-59
lines changed

12 files changed

+266
-59
lines changed

cmd/gator/test/test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/open-policy-agent/frameworks/constraint/pkg/instrumentation"
1111
cmdutils "github.com/open-policy-agent/gatekeeper/v3/cmd/gator/util"
12+
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator"
1213
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator/reader"
1314
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator/test"
1415
"github.com/open-policy-agent/gatekeeper/v3/pkg/util"
@@ -50,13 +51,15 @@ var (
5051
flagTempDir string
5152
flagEnableK8sCel bool
5253
flagDenyOnly bool
54+
flagVerbose bool
5355
)
5456

5557
const (
5658
flagNameFilename = "filename"
5759
flagNameOutput = "output"
5860
flagNameImage = "image"
5961
flagNameTempDir = "tempdir"
62+
flagNameVerbose = "verbose"
6063

6164
stringJSON = "json"
6265
stringYAML = "yaml"
@@ -74,6 +77,7 @@ func init() {
7477
Cmd.Flags().StringArrayVarP(&flagImages, flagNameImage, "i", []string{}, "a URL to an OCI image containing policies. Can be specified multiple times.")
7578
Cmd.Flags().StringVarP(&flagTempDir, flagNameTempDir, "d", "", fmt.Sprintf("Specifies the temporary directory to download and unpack images to, if using the --%s flag. Optional.", flagNameImage))
7679
Cmd.Flags().BoolVarP(&flagDenyOnly, "deny-only", "", false, "output only denied constraints")
80+
Cmd.Flags().BoolVarP(&flagVerbose, flagNameVerbose, "v", false, "print extended test output")
7781
}
7882

7983
func run(_ *cobra.Command, _ []string) {
@@ -85,14 +89,34 @@ func run(_ *cobra.Command, _ []string) {
8589
cmdutils.ErrFatalf("no input data identified")
8690
}
8791

88-
responses, err := test.Test(unstrucs, test.Opts{IncludeTrace: flagIncludeTrace, GatherStats: flagGatherStats, UseK8sCEL: flagEnableK8sCel})
92+
var printBuf bytes.Buffer
93+
94+
var opts []gator.Opt
95+
if flagIncludeTrace {
96+
opts = append(opts, test.WithTrace())
97+
}
98+
if flagGatherStats {
99+
opts = append(opts, test.WithGatherStats())
100+
}
101+
if flagEnableK8sCel {
102+
opts = append(opts, test.WithK8sCEL(flagGatherStats))
103+
}
104+
if flagVerbose {
105+
opts = append(opts, gator.WithPrintHook(&printBuf))
106+
}
107+
108+
responses, err := test.Test(unstrucs, opts...)
89109
if err != nil {
90110
cmdutils.ErrFatalf("auditing objects: %v", err)
91111
}
92112
results := responses.Results()
93113

94114
fmt.Print(formatOutput(flagOutput, results, responses.StatsEntries))
95115

116+
if printBuf.Len() > 0 {
117+
fmt.Printf("\n%s\n", printBuf.String())
118+
}
119+
96120
// Whether or not we return non-zero depends on whether we have a `deny`
97121
// enforcementAction on one of the violated constraints
98122
exitCode := 0

cmd/gator/verify/verify.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,13 @@ func runE(cmd *cobra.Command, args []string) error {
112112
func runSuites(ctx context.Context, fileSystem fs.FS, suites []*verify.Suite, filter verify.Filter) error {
113113
isFailure := false
114114

115-
runner, err := verify.NewRunner(fileSystem, gator.NewOPAClient, verify.IncludeTrace(includeTrace), verify.UseK8sCEL(flagEnableK8sCel))
115+
runner, err := verify.NewRunner(fileSystem, func(opts ...gator.Opt) (gator.Client, error) {
116+
if flagEnableK8sCel {
117+
opts = append(opts, gator.WithK8sCEL())
118+
}
119+
120+
return gator.NewOPAClient(includeTrace, opts...)
121+
}, verify.IncludeTrace(includeTrace))
116122
if err != nil {
117123
return err
118124
}
@@ -136,7 +142,9 @@ func runSuites(ctx context.Context, fileSystem fs.FS, suites []*verify.Suite, fi
136142
results[i] = suiteResult
137143
i++
138144
}
145+
139146
w := &strings.Builder{}
147+
140148
printer := verify.PrinterGo{}
141149
err = printer.Print(w, results, verbose)
142150
if err != nil {

pkg/gator/fixtures/fixtures.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ spec:
3939
rego: |
4040
package k8salwaysvalidate
4141
violation[{"msg": msg}] {
42+
print(sprintf("a debug message (%v)", [input.review.object.kind]))
4243
false
4344
msg := "should always pass"
4445
}

pkg/gator/opa.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,29 @@ import (
66
"github.com/open-policy-agent/gatekeeper/v3/pkg/drivers/k8scel"
77
"github.com/open-policy-agent/gatekeeper/v3/pkg/target"
88
"github.com/open-policy-agent/gatekeeper/v3/pkg/util"
9+
"io"
910
)
1011

11-
func NewOPAClient(includeTrace bool, k8sCEL bool) (Client, error) {
12+
type Opt func() ([]constraintclient.Opt, []rego.Arg, error)
13+
14+
func NewOPAClient(includeTrace bool, opts ...Opt) (Client, error) {
1215
args := []constraintclient.Opt{constraintclient.Targets(&target.K8sValidationTarget{})}
1316

14-
if k8sCEL {
15-
k8sDriver, err := k8scel.New()
17+
driverArgs := []rego.Arg{
18+
rego.Tracing(includeTrace),
19+
}
20+
21+
for _, opt := range opts {
22+
extraArgs, extraDriverArgs, err := opt()
1623
if err != nil {
1724
return nil, err
1825
}
19-
args = append(args, constraintclient.Driver(k8sDriver))
26+
27+
args = append(args, extraArgs...)
28+
driverArgs = append(driverArgs, extraDriverArgs...)
2029
}
2130

22-
driver, err := rego.New(rego.Tracing(includeTrace))
31+
driver, err := rego.New(driverArgs...)
2332
if err != nil {
2433
return nil, err
2534
}
@@ -33,3 +42,25 @@ func NewOPAClient(includeTrace bool, k8sCEL bool) (Client, error) {
3342

3443
return c, nil
3544
}
45+
46+
func WithK8sCEL() Opt {
47+
return func() ([]constraintclient.Opt, []rego.Arg, error) {
48+
k8sDriver, err := k8scel.New()
49+
if err != nil {
50+
return nil, nil, err
51+
}
52+
53+
return []constraintclient.Opt{
54+
constraintclient.Driver(k8sDriver),
55+
}, []rego.Arg{}, nil
56+
}
57+
}
58+
59+
func WithPrintHook(w io.Writer) Opt {
60+
return func() ([]constraintclient.Opt, []rego.Arg, error) {
61+
return []constraintclient.Opt{}, []rego.Arg{
62+
rego.PrintEnabled(true),
63+
rego.PrintHook(NewPrintHook(w)),
64+
}, nil
65+
}
66+
}

pkg/gator/print.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package gator
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
v1 "github.com/open-policy-agent/opa/v1/topdown/print"
8+
)
9+
10+
// PrintHook implements the OPA print hook interface to capture print statement output from Rego policies.
11+
type PrintHook struct {
12+
writer io.Writer
13+
}
14+
15+
// NewPrintHook creates and returns a new instance of PrintHook and writes to writer.
16+
func NewPrintHook(writer io.Writer) PrintHook {
17+
return PrintHook{writer}
18+
}
19+
20+
// Print writes message to writer passed to PrintHook when it was created.
21+
func (h PrintHook) Print(ctx v1.Context, message string) error {
22+
_, err := fmt.Fprintln(h.writer, message)
23+
return err
24+
}

pkg/gator/test/test.go

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/open-policy-agent/frameworks/constraint/pkg/client/reviews"
1111
"github.com/open-policy-agent/gatekeeper/v3/pkg/drivers/k8scel"
1212
"github.com/open-policy-agent/gatekeeper/v3/pkg/expansion"
13+
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator"
1314
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator/expand"
1415
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator/reader"
1516
mutationtypes "github.com/open-policy-agent/gatekeeper/v3/pkg/mutation/types"
@@ -29,28 +30,26 @@ func init() {
2930
}
3031
}
3132

32-
// options for the Test func.
33-
type Opts struct {
34-
// Driver specific options
35-
IncludeTrace bool
36-
GatherStats bool
37-
UseK8sCEL bool
38-
}
39-
40-
func Test(objs []*unstructured.Unstructured, tOpts Opts) (*GatorResponses, error) {
33+
func Test(objs []*unstructured.Unstructured, opts ...gator.Opt) (*GatorResponses, error) {
4134
args := []constraintclient.Opt{constraintclient.Targets(&target.K8sValidationTarget{})}
42-
if tOpts.UseK8sCEL {
43-
k8sDriver, err := makeK8sCELDriver(tOpts)
35+
36+
driverArgs := []rego.Arg{}
37+
38+
for _, opt := range opts {
39+
extraArgs, extraDriverArgs, err := opt()
4440
if err != nil {
45-
return nil, fmt.Errorf("creating K8s native driver: %w", err)
41+
return nil, err
4642
}
47-
args = append(args, constraintclient.Driver(k8sDriver))
43+
44+
args = append(args, extraArgs...)
45+
driverArgs = append(driverArgs, extraDriverArgs...)
4846
}
4947

50-
driver, err := makeRegoDriver(tOpts)
48+
driver, err := rego.New(driverArgs...)
5149
if err != nil {
5250
return nil, fmt.Errorf("creating Rego driver: %w", err)
5351
}
52+
5453
args = append(args, constraintclient.Driver(driver), constraintclient.EnforcementPoints(util.GatorEnforcementPoint))
5554

5655
client, err := constraintclient.NewClient(args...)
@@ -176,24 +175,37 @@ func Test(objs []*unstructured.Unstructured, tOpts Opts) (*GatorResponses, error
176175
return responses, nil
177176
}
178177

179-
func makeRegoDriver(tOpts Opts) (*rego.Driver, error) {
180-
var args []rego.Arg
181-
if tOpts.GatherStats {
182-
args = append(args, rego.GatherStats())
183-
}
184-
if tOpts.IncludeTrace {
185-
args = append(args, rego.Tracing(tOpts.IncludeTrace))
178+
func WithGatherStats() gator.Opt {
179+
return func() ([]constraintclient.Opt, []rego.Arg, error) {
180+
return []constraintclient.Opt{}, []rego.Arg{
181+
rego.GatherStats(),
182+
}, nil
186183
}
184+
}
187185

188-
return rego.New(args...)
186+
func WithTrace() gator.Opt {
187+
return func() ([]constraintclient.Opt, []rego.Arg, error) {
188+
return []constraintclient.Opt{}, []rego.Arg{
189+
rego.Tracing(true),
190+
}, nil
191+
}
189192
}
190193

191-
func makeK8sCELDriver(tOpts Opts) (*k8scel.Driver, error) {
192-
var args []k8scel.Arg
194+
func WithK8sCEL(gatherStats bool) gator.Opt {
195+
return func() ([]constraintclient.Opt, []rego.Arg, error) {
196+
var args []k8scel.Arg
193197

194-
if tOpts.GatherStats {
195-
args = append(args, k8scel.GatherStats())
196-
}
198+
if gatherStats {
199+
args = append(args, k8scel.GatherStats())
200+
}
197201

198-
return k8scel.New(args...)
202+
k8sDriver, err := k8scel.New(args...)
203+
if err != nil {
204+
return []constraintclient.Opt{}, []rego.Arg{}, fmt.Errorf("creating K8s native driver: %w", err)
205+
}
206+
207+
return []constraintclient.Opt{
208+
constraintclient.Driver(k8sDriver),
209+
}, []rego.Arg{}, nil
210+
}
199211
}

pkg/gator/test/test_test.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package test
22

33
import (
4+
"bytes"
5+
"fmt"
6+
"strings"
47
"testing"
58

69
"github.com/google/go-cmp/cmp"
710
"github.com/google/go-cmp/cmp/cmpopts"
811
constraintclient "github.com/open-policy-agent/frameworks/constraint/pkg/client"
912
"github.com/open-policy-agent/frameworks/constraint/pkg/instrumentation"
1013
"github.com/open-policy-agent/frameworks/constraint/pkg/types"
14+
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator"
1115
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator/fixtures"
1216
"github.com/open-policy-agent/gatekeeper/v3/pkg/gator/reader"
1317
"github.com/open-policy-agent/gatekeeper/v3/pkg/target"
@@ -245,7 +249,7 @@ func TestTest(t *testing.T) {
245249
objs = append(objs, u)
246250
}
247251

248-
resps, err := Test(objs, Opts{})
252+
resps, err := Test(objs)
249253
if tc.err != nil {
250254
require.ErrorIs(t, err, tc.err)
251255
} else if err != nil {
@@ -285,7 +289,7 @@ func Test_Test_withTrace(t *testing.T) {
285289
objs = append(objs, u)
286290
}
287291

288-
resps, err := Test(objs, Opts{IncludeTrace: true})
292+
resps, err := Test(objs, WithTrace())
289293
if err != nil {
290294
t.Errorf("got err '%v', want nil", err)
291295
}
@@ -346,7 +350,7 @@ func Test_Test_withStats(t *testing.T) {
346350
objs = append(objs, u)
347351
}
348352

349-
resps, err := Test(objs, Opts{GatherStats: true})
353+
resps, err := Test(objs, WithGatherStats())
350354
assert.NoError(t, err)
351355

352356
actualStats := resps.StatsEntries
@@ -411,3 +415,38 @@ func Test_Test_withStats(t *testing.T) {
411415
}
412416
}
413417
}
418+
419+
func TestTest_Print(t *testing.T) {
420+
var printBuf bytes.Buffer
421+
422+
inputs := []string{
423+
fixtures.TemplateAlwaysValidate,
424+
fixtures.ConstraintAlwaysValidate,
425+
fixtures.Object,
426+
}
427+
428+
var objs []*unstructured.Unstructured
429+
for _, input := range inputs {
430+
u, err := reader.ReadUnstructured([]byte(input))
431+
if err != nil {
432+
t.Fatalf("readUnstructured for input %q: %v", input, err)
433+
}
434+
objs = append(objs, u)
435+
}
436+
437+
_, err := Test(objs, gator.WithPrintHook(&printBuf))
438+
if err != nil {
439+
t.Errorf("got err '%v', want nil", err)
440+
}
441+
442+
// The constraint template results in three debug statements. The use of the object's kind is only for
443+
// illustration purposes to make it clear three debug statements being written is not a bug.
444+
want := strings.Builder{}
445+
for _, kind := range []string{"ConstraintTemplate", "AlwaysValidate", "Object"} {
446+
want.WriteString(fmt.Sprintf("a debug message (%s)\n", kind))
447+
}
448+
449+
if diff := cmp.Diff(want.String(), printBuf.String()); diff != "" {
450+
t.Fatalf("diff in print statements (-want +got)\n%s", diff)
451+
}
452+
}

pkg/gator/verify/printer_go.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ func (p PrinterGo) PrintCase(w StringWriter, r *CaseResult, verbose bool) error
127127
if err != nil {
128128
return fmt.Errorf("%w: %w", ErrWritingString, err)
129129
}
130+
131+
if len(r.Print) > 0 {
132+
_, err = w.WriteString(" === PRINT ===\n")
133+
if err != nil {
134+
return fmt.Errorf("%w: %w", ErrWritingString, err)
135+
}
136+
137+
for _, line := range strings.Split(strings.TrimSuffix(r.Print, "\n"), "\n") {
138+
_, err = w.WriteString(fmt.Sprintf(" %s\n", line))
139+
if err != nil {
140+
return fmt.Errorf("%w: %w", ErrWritingString, err)
141+
}
142+
}
143+
}
130144
}
131145

132146
if r.Error != nil {

0 commit comments

Comments
 (0)