mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-13 03:42:35 -06:00
db035b09fc
Makes OPA build and pass its tests on Go 1.27, while keeping Go 1.25 and 1.26 working. JSON output is unchanged on every supported version. Go 1.27 json package honours `encoding.TextAppender`. Many v1 ast types implement AppendText to build their Rego string cheaply, so on 1.27 they would have marshalled as Rego text. Files built only with 1.27 now implement MarshalJSONTo. Library users should keep using `json.Marshal` etc. The MarshalJSONTo methods are implementation details, are absent from 1.25 and 1.26 builds, and may change. --------- Signed-off-by: Anders Eknert <anders.eknert@apple.com> Signed-off-by: Charlie Egan <charlie_egan@apple.com> Co-authored-by: Charlie Egan <charlie_egan@apple.com>
2300 lines
47 KiB
Go
2300 lines
47 KiB
Go
//go:build !go1.27
|
|
|
|
// Copyright 2021 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"maps"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/open-policy-agent/opa/cmd/formats"
|
|
"github.com/open-policy-agent/opa/internal/file/archive"
|
|
"github.com/open-policy-agent/opa/v1/util"
|
|
|
|
"github.com/open-policy-agent/opa/v1/util/test"
|
|
)
|
|
|
|
func TestDoInspectJSONOutputBytes(t *testing.T) {
|
|
files := [][2]string{
|
|
{"/.manifest", `{"revision": "rev", "roots": ["foo", "bar", "fuz", "baz", "a", "x"]}`},
|
|
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}`},
|
|
{"/example/foo.rego", `package foo`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
|
|
if err := os.WriteFile(bundleFile, buf.Bytes(), 0o644); err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
params := newInspectCommandParams()
|
|
if err := params.outputFormat.Set(formats.JSON); err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
if err := doInspect(params, bundleFile, &out); err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
expected := `{
|
|
"manifest": {
|
|
"revision": "rev",
|
|
"roots": [
|
|
"foo",
|
|
"bar",
|
|
"fuz",
|
|
"baz",
|
|
"a",
|
|
"x"
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data": [
|
|
"/data.json"
|
|
],
|
|
"data.foo": [
|
|
"/example/foo.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}
|
|
`
|
|
if diff := cmp.Diff(expected, out.String()); diff != "" {
|
|
t.Errorf("unexpected result (-want, +got):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func TestDoInspect(t *testing.T) {
|
|
files := [][2]string{
|
|
{"/.manifest", `{"revision": "rev", "roots": ["foo", "bar", "fuz", "baz", "a", "x"]}`},
|
|
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}`},
|
|
{"/example/foo.rego", `package foo`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
|
|
rootDir := t.TempDir()
|
|
bundleFile := filepath.Join(rootDir, "bundle.tar.gz")
|
|
|
|
bf, err := os.Create(bundleFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
_, err = bf.Write(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
params := newInspectCommandParams()
|
|
err = params.outputFormat.Set(formats.JSON)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
err = doInspect(params, bundleFile, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
res := `{
|
|
"capabilities": {"features": ["rego_v1"]},
|
|
"manifest": {"revision": "rev", "roots": ["foo", "bar", "fuz", "baz", "a", "x"]},
|
|
"signatures_config": {},
|
|
"namespaces": {"data": ["/data.json"], "data.foo": ["/example/foo.rego"]}
|
|
}`
|
|
|
|
exp := util.MustUnmarshalJSON([]byte(res))
|
|
result := util.MustUnmarshalJSON(out.Bytes())
|
|
if !reflect.DeepEqual(exp, result) {
|
|
t.Fatalf("expected inspect output to be:\n\n%v\n\ngot:\n\n%v", exp, result)
|
|
}
|
|
}
|
|
|
|
func TestDoInspectPretty(t *testing.T) {
|
|
|
|
root := fmt.Sprintf("metadata/%v/features", strings.Repeat("foobar", 20))
|
|
|
|
manifest := fmt.Sprintf(`{"revision": "%s",
|
|
"roots": ["foo", "bar", "fuz", "http", "a", "x", "%s"],
|
|
"metadata": {"hello": "%s"},
|
|
"wasm": [{"entrypoint": "http/example/authz", "module": "/policy.wasm"}, {"entrypoint": "http/example/foo/allow", "module": "/example/policy.wasm"}]}`, strings.Repeat("foobar", 10), root, strings.Repeat("world", 100))
|
|
|
|
files := [][2]string{
|
|
{"/.manifest", manifest},
|
|
{"/data.json", `{"x": {"y": true}, "a": {"b": {"z": true}}}`},
|
|
{"/http/example/authz/foo.rego", `package http.example.authz`},
|
|
{"/http/example/authz/data.json", `{"faz": "baz"}`},
|
|
{"/example/foo.rego", `package foo`},
|
|
{"/a/b/y/foo.rego", `package a.b.y`},
|
|
{"/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego", `package a.b.y`},
|
|
{"/example/policy.wasm", `modules-compiled-as-wasm-binary`},
|
|
{"/http/example/policy.wasm", `modules-compiled-as-wasm-binary`},
|
|
{"/policy.wasm", `modules-compiled-as-wasm-binary`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
|
|
|
|
bf, err := os.Create(bundleFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
_, err = bf.Write(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
err = doInspect(newInspectCommandParams(), bundleFile, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
output := strings.TrimSpace(out.String())
|
|
expected := strings.TrimSpace(`
|
|
MANIFEST:
|
|
┌──────────┬────────────────────────────────────────────────────┐
|
|
│ FIELD │ VALUE │
|
|
├──────────┼────────────────────────────────────────────────────┤
|
|
│ Revision │ foobarfoobarfoobarfoobarfoobarfoobarfoobarfooba... │
|
|
│ Roots │ a │
|
|
│ │ bar │
|
|
│ │ foo │
|
|
│ │ fuz │
|
|
│ │ http │
|
|
│ │ metadata/...oobarfoobarfoobarfoobarfoobar/features │
|
|
│ │ x │
|
|
│ Metadata │ {"hello":"worldworldworldworldworldworldworldwo... │
|
|
└──────────┴────────────────────────────────────────────────────┘
|
|
NAMESPACES:
|
|
┌─────────────────────────────┬────────────────────────────────────────────────────┐
|
|
│ NAMESPACE │ FILE │
|
|
├─────────────────────────────┼────────────────────────────────────────────────────┤
|
|
│ data │ /data.json │
|
|
│ data.a.b.y │ /a/b/y/foo.rego │
|
|
│ │ /a/...xxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego │
|
|
│ data.foo │ /example/foo.rego │
|
|
│ data.http.example.authz │ /http/example/authz/foo.rego │
|
|
│ │ /http/example/authz/data.json │
|
|
│ │ /policy.wasm │
|
|
│ data.http.example.foo.allow │ /example/policy.wasm │
|
|
└─────────────────────────────┴────────────────────────────────────────────────────┘
|
|
`)
|
|
|
|
if output != expected {
|
|
t.Fatalf("Unexpected output. Expected:\n\n%v\n\nGot:\n\n%v", expected, output)
|
|
}
|
|
}
|
|
|
|
func TestDoInspectPrettyManifestOnlySingleRoot(t *testing.T) {
|
|
root := fmt.Sprintf("metadata/%v/features", strings.Repeat("foobar", 6))
|
|
|
|
manifest := fmt.Sprintf(`{"roots": ["%s"],
|
|
"metadata": {"hello": "world"}}`, root)
|
|
|
|
files := [][2]string{
|
|
{"/.manifest", manifest},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
|
|
|
|
bf, err := os.Create(bundleFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
_, err = bf.Write(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
err = doInspect(newInspectCommandParams(), bundleFile, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
output := strings.TrimSpace(out.String())
|
|
expected := strings.TrimSpace(`
|
|
MANIFEST:
|
|
┌──────────┬────────────────────────────────────────────────────┐
|
|
│ FIELD │ VALUE │
|
|
├──────────┼────────────────────────────────────────────────────┤
|
|
│ Roots │ metadata/...oobarfoobarfoobarfoobarfoobar/features │
|
|
│ Metadata │ {"hello":"world"} │
|
|
└──────────┴────────────────────────────────────────────────────┘
|
|
`)
|
|
|
|
if output != expected {
|
|
t.Fatalf("Unexpected output. Expected:\n\n%v\n\nGot:\n\n%v", expected, output)
|
|
}
|
|
}
|
|
|
|
func TestInspectMultiBundleError(t *testing.T) {
|
|
params := newInspectCommandParams()
|
|
err := validateInspectParams(¶ms, []string{"foo", "bar"})
|
|
if err == nil {
|
|
t.Fatal("Expected error but got nil")
|
|
}
|
|
|
|
exp := "specify exactly one OPA bundle or path"
|
|
if err.Error() != exp {
|
|
t.Fatalf("Expected error %v but got %v", exp, err.Error())
|
|
}
|
|
}
|
|
|
|
func TestDoInspectWithAnnotations(t *testing.T) {
|
|
|
|
files := map[string]string{
|
|
"x.rego": `# METADATA
|
|
# title: pkg-title
|
|
# description: pkg-descr
|
|
# organizations:
|
|
# - pkg-org
|
|
# related_resources:
|
|
# - https://pkg
|
|
# - ref: https://pkg
|
|
# description: rr-pkg-note
|
|
# authors:
|
|
# - pkg-author
|
|
# schemas:
|
|
# - input: {"type": "boolean"}
|
|
# custom:
|
|
# pkg: pkg-custom
|
|
package test
|
|
|
|
# METADATA
|
|
# scope: document
|
|
# title: doc-title
|
|
# description: doc-descr
|
|
# organizations:
|
|
# - doc-org
|
|
# related_resources:
|
|
# - https://doc
|
|
# - ref: https://doc
|
|
# description: rr-doc-note
|
|
# authors:
|
|
# - doc-author
|
|
# schemas:
|
|
# - input: {"type": "integer"}
|
|
# custom:
|
|
# doc: doc-custom
|
|
|
|
# METADATA
|
|
# title: rule-title
|
|
# description: rule-title
|
|
# organizations:
|
|
# - rule-org
|
|
# related_resources:
|
|
# - https://rule
|
|
# - ref: https://rule
|
|
# description: rr-rule-note
|
|
# authors:
|
|
# - rule-author
|
|
# schemas:
|
|
# - input: {"type": "string"}
|
|
# custom:
|
|
# rule: rule-custom
|
|
p = 1`,
|
|
}
|
|
|
|
t.Run("pretty", func(t *testing.T) {
|
|
test.WithTempFS(files, func(rootDir string) {
|
|
ps := newInspectCommandParams()
|
|
ps.listAnnotations = true
|
|
var out bytes.Buffer
|
|
err := doInspect(ps, rootDir, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
bs := out.Bytes()
|
|
idx := bytes.Index(bs, []byte(`ANNOTATIONS`)) // skip NAMESPACE box
|
|
output := strings.TrimSpace(string(bs[idx:]))
|
|
expected := strings.TrimSpace(fmt.Sprintf(`
|
|
ANNOTATIONS:
|
|
pkg-title
|
|
=========
|
|
|
|
pkg-descr
|
|
|
|
Package: test
|
|
Location: %[1]s/x.rego:16
|
|
Scope: package
|
|
|
|
Organizations:
|
|
pkg-org
|
|
|
|
Authors:
|
|
pkg-author
|
|
|
|
Schemas:
|
|
input: {"type":"boolean"}
|
|
|
|
Related Resources:
|
|
https://pkg
|
|
https://pkg rr-pkg-note
|
|
|
|
Custom:
|
|
pkg: "pkg-custom"
|
|
|
|
doc-title
|
|
=========
|
|
|
|
doc-descr
|
|
|
|
Package: test
|
|
Rule: p
|
|
Location: %[1]s/x.rego:50
|
|
Scope: document
|
|
|
|
Organizations:
|
|
doc-org
|
|
|
|
Authors:
|
|
doc-author
|
|
|
|
Schemas:
|
|
input: {"type":"integer"}
|
|
|
|
Related Resources:
|
|
https://doc
|
|
https://doc rr-doc-note
|
|
|
|
Custom:
|
|
doc: "doc-custom"
|
|
|
|
rule-title
|
|
==========
|
|
|
|
rule-title
|
|
|
|
Package: test
|
|
Rule: p
|
|
Location: %[1]s/x.rego:50
|
|
Scope: rule
|
|
|
|
Organizations:
|
|
rule-org
|
|
|
|
Authors:
|
|
rule-author
|
|
|
|
Schemas:
|
|
input: {"type":"string"}
|
|
|
|
Related Resources:
|
|
https://rule
|
|
https://rule rr-rule-note
|
|
|
|
Custom:
|
|
rule: "rule-custom"`, rootDir))
|
|
|
|
if output != expected {
|
|
t.Fatalf("Unexpected output. Expected:\n\n%q\n\nGot:\n\n%q", expected, output)
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("json", func(t *testing.T) {
|
|
test.WithTempFS(files, func(rootDir string) {
|
|
ps := newInspectCommandParams()
|
|
ps.listAnnotations = true
|
|
err := ps.outputFormat.Set(formats.JSON)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
var out bytes.Buffer
|
|
err = doInspect(ps, rootDir, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
bs := out.Bytes()
|
|
expected := strings.TrimSpace(fmt.Sprintf(`{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"%[1]s/x.rego"
|
|
]
|
|
},
|
|
"annotations": [
|
|
{
|
|
"annotations": {
|
|
"authors": [
|
|
{
|
|
"name": "pkg-author"
|
|
}
|
|
],
|
|
"custom": {
|
|
"pkg": "pkg-custom"
|
|
},
|
|
"description": "pkg-descr",
|
|
"organizations": [
|
|
"pkg-org"
|
|
],
|
|
"related_resources": [
|
|
{
|
|
"ref": "https://pkg"
|
|
},
|
|
{
|
|
"description": "rr-pkg-note",
|
|
"ref": "https://pkg"
|
|
}
|
|
],
|
|
"schemas": [
|
|
{
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "input"
|
|
}
|
|
],
|
|
"definition": {
|
|
"type": "boolean"
|
|
}
|
|
}
|
|
],
|
|
"scope": "package",
|
|
"title": "pkg-title"
|
|
},
|
|
"location": {
|
|
"file": "%[1]s/x.rego",
|
|
"row": 16,
|
|
"col": 1
|
|
},
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "test"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"annotations": {
|
|
"authors": [
|
|
{
|
|
"name": "doc-author"
|
|
}
|
|
],
|
|
"custom": {
|
|
"doc": "doc-custom"
|
|
},
|
|
"description": "doc-descr",
|
|
"organizations": [
|
|
"doc-org"
|
|
],
|
|
"related_resources": [
|
|
{
|
|
"ref": "https://doc"
|
|
},
|
|
{
|
|
"description": "rr-doc-note",
|
|
"ref": "https://doc"
|
|
}
|
|
],
|
|
"schemas": [
|
|
{
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "input"
|
|
}
|
|
],
|
|
"definition": {
|
|
"type": "integer"
|
|
}
|
|
}
|
|
],
|
|
"scope": "document",
|
|
"title": "doc-title"
|
|
},
|
|
"location": {
|
|
"file": "%[1]s/x.rego",
|
|
"row": 50,
|
|
"col": 1
|
|
},
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "test"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "p"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"annotations": {
|
|
"authors": [
|
|
{
|
|
"name": "rule-author"
|
|
}
|
|
],
|
|
"custom": {
|
|
"rule": "rule-custom"
|
|
},
|
|
"description": "rule-title",
|
|
"organizations": [
|
|
"rule-org"
|
|
],
|
|
"related_resources": [
|
|
{
|
|
"ref": "https://rule"
|
|
},
|
|
{
|
|
"description": "rr-rule-note",
|
|
"ref": "https://rule"
|
|
}
|
|
],
|
|
"schemas": [
|
|
{
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "input"
|
|
}
|
|
],
|
|
"definition": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
],
|
|
"scope": "rule",
|
|
"title": "rule-title"
|
|
},
|
|
"location": {
|
|
"file": "%[1]s/x.rego",
|
|
"row": 50,
|
|
"col": 1
|
|
},
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "test"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "p"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"capabilities": {
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`, rootDir))
|
|
|
|
exp := util.MustUnmarshalJSON([]byte(expected))
|
|
result := util.MustUnmarshalJSON(bs)
|
|
if !reflect.DeepEqual(exp, result) {
|
|
t.Fatalf("expected inspect output to be:\n\n%v\n\ngot:\n\n%v", exp, result)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestDoInspectTarballWithAnnotations(t *testing.T) {
|
|
|
|
files := [][2]string{
|
|
{"x.rego", `# METADATA
|
|
# title: pkg-title
|
|
# description: pkg-descr
|
|
# organizations:
|
|
# - pkg-org
|
|
# related_resources:
|
|
# - https://pkg
|
|
# - ref: https://pkg
|
|
# description: rr-pkg-note
|
|
# authors:
|
|
# - pkg-author
|
|
# schemas:
|
|
# - input: {"type": "boolean"}
|
|
# custom:
|
|
# pkg: pkg-custom
|
|
package test
|
|
|
|
# METADATA
|
|
# scope: document
|
|
# title: doc-title
|
|
# description: doc-descr
|
|
# organizations:
|
|
# - doc-org
|
|
# related_resources:
|
|
# - https://doc
|
|
# - ref: https://doc
|
|
# description: rr-doc-note
|
|
# authors:
|
|
# - doc-author
|
|
# schemas:
|
|
# - input: {"type": "integer"}
|
|
# custom:
|
|
# doc: doc-custom
|
|
|
|
# METADATA
|
|
# title: rule-title
|
|
# description: rule-title
|
|
# organizations:
|
|
# - rule-org
|
|
# related_resources:
|
|
# - https://rule
|
|
# - ref: https://rule
|
|
# description: rr-rule-note
|
|
# authors:
|
|
# - rule-author
|
|
# schemas:
|
|
# - input: {"type": "string"}
|
|
# custom:
|
|
# rule: rule-custom
|
|
p = 1`},
|
|
{".manifest", `
|
|
{
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
],
|
|
"wasm": [
|
|
{
|
|
"entrypoint": "test/a",
|
|
"module": "/policy.wasm"
|
|
},
|
|
{
|
|
"entrypoint": "test/b",
|
|
"module": "/policy.wasm",
|
|
"annotations": [
|
|
{
|
|
"scope": "rule",
|
|
"title": "WASM RULE B",
|
|
"entrypoint": true
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`},
|
|
{"policy.wasm", ""},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
|
|
t.Run("pretty", func(t *testing.T) {
|
|
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
|
|
|
|
bf, err := os.Create(bundleFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
_, err = bf.Write(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
ps := newInspectCommandParams()
|
|
ps.listAnnotations = true
|
|
var out bytes.Buffer
|
|
|
|
err = doInspect(ps, bundleFile, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
bs := out.Bytes()
|
|
idx := bytes.Index(bs, []byte(`ANNOTATIONS`)) // skip NAMESPACE box
|
|
output := strings.TrimSpace(string(bs[idx:]))
|
|
expected := strings.TrimSpace(`
|
|
ANNOTATIONS:
|
|
pkg-title
|
|
=========
|
|
|
|
pkg-descr
|
|
|
|
Package: test
|
|
Location: /x.rego:16
|
|
Scope: package
|
|
|
|
Organizations:
|
|
pkg-org
|
|
|
|
Authors:
|
|
pkg-author
|
|
|
|
Schemas:
|
|
input: {"type":"boolean"}
|
|
|
|
Related Resources:
|
|
https://pkg
|
|
https://pkg rr-pkg-note
|
|
|
|
Custom:
|
|
pkg: "pkg-custom"
|
|
|
|
WASM RULE B
|
|
===========
|
|
|
|
Location: /policy.wasm:0
|
|
Scope: rule
|
|
Entrypoint: true
|
|
|
|
doc-title
|
|
=========
|
|
|
|
doc-descr
|
|
|
|
Package: test
|
|
Rule: p
|
|
Location: /x.rego:50
|
|
Scope: document
|
|
|
|
Organizations:
|
|
doc-org
|
|
|
|
Authors:
|
|
doc-author
|
|
|
|
Schemas:
|
|
input: {"type":"integer"}
|
|
|
|
Related Resources:
|
|
https://doc
|
|
https://doc rr-doc-note
|
|
|
|
Custom:
|
|
doc: "doc-custom"
|
|
|
|
rule-title
|
|
==========
|
|
|
|
rule-title
|
|
|
|
Package: test
|
|
Rule: p
|
|
Location: /x.rego:50
|
|
Scope: rule
|
|
|
|
Organizations:
|
|
rule-org
|
|
|
|
Authors:
|
|
rule-author
|
|
|
|
Schemas:
|
|
input: {"type":"string"}
|
|
|
|
Related Resources:
|
|
https://rule
|
|
https://rule rr-rule-note
|
|
|
|
Custom:
|
|
rule: "rule-custom"`)
|
|
|
|
if output != expected {
|
|
t.Fatalf("Unexpected output. Expected:\n\n%q\n\nGot:\n\n%q", expected, output)
|
|
}
|
|
|
|
})
|
|
|
|
t.Run("json", func(t *testing.T) {
|
|
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
|
|
|
|
bf, err := os.Create(bundleFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
_, err = bf.Write(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
ps := newInspectCommandParams()
|
|
ps.listAnnotations = true
|
|
err = ps.outputFormat.Set(formats.JSON)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
var out bytes.Buffer
|
|
|
|
err = doInspect(ps, bundleFile, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
expected := strings.TrimSpace(fmt.Sprintf(`{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
],
|
|
"wasm": [
|
|
{
|
|
"entrypoint": "test/a",
|
|
"module": "/policy.wasm"
|
|
},
|
|
{
|
|
"entrypoint": "test/b",
|
|
"module": "/policy.wasm",
|
|
"annotations": [
|
|
{
|
|
"entrypoint": true,
|
|
"scope": "rule",
|
|
"title": "WASM RULE B"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"wasm_modules": [
|
|
{
|
|
"entrypoints": [
|
|
"data.test.a",
|
|
"data.test.b"
|
|
],
|
|
"path": "/policy.wasm",
|
|
"url": "%[1]s/policy.wasm"
|
|
}
|
|
],
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/x.rego"
|
|
],
|
|
"data.test.a": [
|
|
"/policy.wasm"
|
|
],
|
|
"data.test.b": [
|
|
"/policy.wasm"
|
|
]
|
|
},
|
|
"annotations": [
|
|
{
|
|
"annotations": {
|
|
"authors": [
|
|
{
|
|
"name": "pkg-author"
|
|
}
|
|
],
|
|
"custom": {
|
|
"pkg": "pkg-custom"
|
|
},
|
|
"description": "pkg-descr",
|
|
"organizations": [
|
|
"pkg-org"
|
|
],
|
|
"related_resources": [
|
|
{
|
|
"ref": "https://pkg"
|
|
},
|
|
{
|
|
"description": "rr-pkg-note",
|
|
"ref": "https://pkg"
|
|
}
|
|
],
|
|
"schemas": [
|
|
{
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "input"
|
|
}
|
|
],
|
|
"definition": {
|
|
"type": "boolean"
|
|
}
|
|
}
|
|
],
|
|
"scope": "package",
|
|
"title": "pkg-title"
|
|
},
|
|
"location": {
|
|
"file": "/x.rego",
|
|
"row": 16,
|
|
"col": 1
|
|
},
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "test"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"annotations": {
|
|
"entrypoint": true,
|
|
"scope": "rule",
|
|
"title": "WASM RULE B"
|
|
},
|
|
"location": {
|
|
"file": "/policy.wasm",
|
|
"row": 0,
|
|
"col": 0
|
|
},
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "test"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "b"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"annotations": {
|
|
"authors": [
|
|
{
|
|
"name": "doc-author"
|
|
}
|
|
],
|
|
"custom": {
|
|
"doc": "doc-custom"
|
|
},
|
|
"description": "doc-descr",
|
|
"organizations": [
|
|
"doc-org"
|
|
],
|
|
"related_resources": [
|
|
{
|
|
"ref": "https://doc"
|
|
},
|
|
{
|
|
"description": "rr-doc-note",
|
|
"ref": "https://doc"
|
|
}
|
|
],
|
|
"schemas": [
|
|
{
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "input"
|
|
}
|
|
],
|
|
"definition": {
|
|
"type": "integer"
|
|
}
|
|
}
|
|
],
|
|
"scope": "document",
|
|
"title": "doc-title"
|
|
},
|
|
"location": {
|
|
"file": "/x.rego",
|
|
"row": 50,
|
|
"col": 1
|
|
},
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "test"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "p"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"annotations": {
|
|
"authors": [
|
|
{
|
|
"name": "rule-author"
|
|
}
|
|
],
|
|
"custom": {
|
|
"rule": "rule-custom"
|
|
},
|
|
"description": "rule-title",
|
|
"organizations": [
|
|
"rule-org"
|
|
],
|
|
"related_resources": [
|
|
{
|
|
"ref": "https://rule"
|
|
},
|
|
{
|
|
"description": "rr-rule-note",
|
|
"ref": "https://rule"
|
|
}
|
|
],
|
|
"schemas": [
|
|
{
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "input"
|
|
}
|
|
],
|
|
"definition": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
],
|
|
"scope": "rule",
|
|
"title": "rule-title"
|
|
},
|
|
"location": {
|
|
"file": "/x.rego",
|
|
"row": 50,
|
|
"col": 1
|
|
},
|
|
"path": [
|
|
{
|
|
"type": "var",
|
|
"value": "data"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "test"
|
|
},
|
|
{
|
|
"type": "string",
|
|
"value": "p"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"capabilities": {
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`, bundleFile))
|
|
exp := util.MustUnmarshalJSON([]byte(expected))
|
|
|
|
result := util.MustUnmarshalJSON(out.Bytes())
|
|
if !reflect.DeepEqual(exp, result) {
|
|
t.Fatalf("expected inspect output to be:\n\n%v\n\ngot:\n\n%v", exp, result)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDoInspect_V0Compatible(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
v0Compatible bool
|
|
module string
|
|
expErrs []string
|
|
}{
|
|
{
|
|
note: "v0, keywords not used",
|
|
v0Compatible: true,
|
|
module: `package test
|
|
p[v] {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v0, no keywords imported, but used",
|
|
v0Compatible: true,
|
|
module: `package test
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
expErrs: []string{
|
|
"rego_parse_error: var cannot be used for rule name",
|
|
},
|
|
},
|
|
{
|
|
note: "v0, keywords imported",
|
|
module: `package test
|
|
import future.keywords
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v0, rego.v1 imported",
|
|
module: `package test
|
|
import rego.v1
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v1, keywords not used",
|
|
module: `package test
|
|
p[v] {
|
|
v := input.x
|
|
}`,
|
|
expErrs: []string{
|
|
"rego_parse_error: `if` keyword is required before rule body",
|
|
"rego_parse_error: `contains` keyword is required for partial set rules",
|
|
},
|
|
},
|
|
{
|
|
note: "v1, no keywords imported",
|
|
module: `package test
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v1, keywords imported",
|
|
module: `package test
|
|
import future.keywords
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
{
|
|
note: "v1, rego.v1 imported",
|
|
module: `package test
|
|
import rego.v1
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
buf := archive.MustWriteTarGz([][2]string{{"/policy.rego", tc.module}})
|
|
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
|
|
|
|
bf, err := os.Create(bundleFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
_, err = bf.Write(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
params := newInspectCommandParams()
|
|
params.v0Compatible = tc.v0Compatible
|
|
err = params.outputFormat.Set(formats.JSON)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
err = doInspect(params, bundleFile, &out)
|
|
if len(tc.expErrs) > 0 {
|
|
if err == nil {
|
|
t.Fatalf("Expected error but got nil")
|
|
}
|
|
|
|
for _, expErr := range tc.expErrs {
|
|
if !strings.Contains(err.Error(), expErr) {
|
|
t.Fatalf("Expected error:\n\n%v\n\nbut got:\n\n%v", expErr, err.Error())
|
|
}
|
|
}
|
|
} else if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDoInspectWithBundleRegoVersion(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
bundleRegoVersion int
|
|
files map[string]string
|
|
expErrs []string
|
|
}{
|
|
{
|
|
note: "v0.x bundle, keywords not used",
|
|
bundleRegoVersion: 0,
|
|
files: map[string]string{
|
|
".manifest": `{"rego_version": 0}`,
|
|
"policy.rego": `package test
|
|
p[v] {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v0.x bundle, no keywords imported, but used",
|
|
bundleRegoVersion: 0,
|
|
files: map[string]string{
|
|
".manifest": `{"rego_version": 0}`,
|
|
"policy.rego": `package test
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
expErrs: []string{
|
|
"rego_parse_error: var cannot be used for rule name",
|
|
},
|
|
},
|
|
{
|
|
note: "v0.x bundle, keywords imported",
|
|
bundleRegoVersion: 0,
|
|
files: map[string]string{
|
|
".manifest": `{"rego_version": 0}`,
|
|
"policy.rego": `package test
|
|
import future.keywords
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v0.x bundle, rego.v1 imported",
|
|
bundleRegoVersion: 0,
|
|
files: map[string]string{
|
|
".manifest": `{"rego_version": 0}`,
|
|
"policy.rego": `package test
|
|
import rego.v1
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v0 bundle, v1 per-file override",
|
|
bundleRegoVersion: 0,
|
|
files: map[string]string{
|
|
".manifest": `{
|
|
"rego_version": 0,
|
|
"file_rego_versions": {
|
|
"/policy2.rego": 1
|
|
}
|
|
}`,
|
|
"policy1.rego": `package test
|
|
p[1] {
|
|
v := input.x
|
|
}`,
|
|
"policy2.rego": `package test
|
|
p contains 2 if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v0 bundle, v1 per-file override (glob)",
|
|
bundleRegoVersion: 0,
|
|
files: map[string]string{
|
|
".manifest": `{
|
|
"rego_version": 0,
|
|
"file_rego_versions": {
|
|
"/bar/*.rego": 1
|
|
}
|
|
}`,
|
|
"foo/policy1.rego": `package test
|
|
p[1] {
|
|
v := input.x
|
|
}`,
|
|
"bar/policy2.rego": `package test
|
|
p contains 2 if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v0 bundle, v1 per-file override, incompatible",
|
|
bundleRegoVersion: 0,
|
|
files: map[string]string{
|
|
".manifest": `{
|
|
"rego_version": 0,
|
|
"file_rego_versions": {
|
|
"/policy2.rego": 1
|
|
}
|
|
}`,
|
|
"policy1.rego": `package test
|
|
p[1] {
|
|
v := input.x
|
|
}`,
|
|
"policy2.rego": `package test
|
|
p[2] {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
expErrs: []string{
|
|
"rego_parse_error: `if` keyword is required before rule body",
|
|
"rego_parse_error: `contains` keyword is required for partial set rules",
|
|
},
|
|
},
|
|
{
|
|
note: "v1.0 bundle, keywords not used",
|
|
bundleRegoVersion: 1,
|
|
files: map[string]string{
|
|
".manifest": `{"rego_version": 1}`,
|
|
"policy.rego": `package test
|
|
p[v] {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
expErrs: []string{
|
|
"rego_parse_error: `if` keyword is required before rule body",
|
|
"rego_parse_error: `contains` keyword is required for partial set rules",
|
|
},
|
|
},
|
|
{
|
|
note: "v1.0 bundle, no keywords imported",
|
|
bundleRegoVersion: 1,
|
|
files: map[string]string{
|
|
".manifest": `{"rego_version": 1}`,
|
|
"policy.rego": `package test
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v1.0 bundle, keywords imported",
|
|
bundleRegoVersion: 1,
|
|
files: map[string]string{
|
|
".manifest": `{"rego_version": 1}`,
|
|
"policy.rego": `package test
|
|
import future.keywords
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v1.0 bundle, rego.v1 imported",
|
|
bundleRegoVersion: 1,
|
|
files: map[string]string{
|
|
".manifest": `{"rego_version": 1}`,
|
|
"policy.rego": `package test
|
|
import rego.v1
|
|
p contains v if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v1 bundle, v0 per-file override",
|
|
bundleRegoVersion: 1,
|
|
files: map[string]string{
|
|
".manifest": `{
|
|
"rego_version": 1,
|
|
"file_rego_versions": {
|
|
"/policy1.rego": 0
|
|
}
|
|
}`,
|
|
"policy1.rego": `package test
|
|
p[1] {
|
|
v := input.x
|
|
}`,
|
|
"policy2.rego": `package test
|
|
p contains 2 if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v1 bundle, v0 per-file override",
|
|
bundleRegoVersion: 1,
|
|
files: map[string]string{
|
|
".manifest": `{
|
|
"rego_version": 1,
|
|
"file_rego_versions": {
|
|
"/foo/*.rego": 0
|
|
}
|
|
}`,
|
|
"foo/policy1.rego": `package test
|
|
p[1] {
|
|
v := input.x
|
|
}`,
|
|
"bar/policy2.rego": `package test
|
|
p contains 2 if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
},
|
|
{
|
|
note: "v1 bundle, v0 per-file override, incompatible",
|
|
bundleRegoVersion: 1,
|
|
files: map[string]string{
|
|
".manifest": `{
|
|
"rego_version": 1,
|
|
"file_rego_versions": {
|
|
"/policy1.rego": 0
|
|
}
|
|
}`,
|
|
"policy1.rego": `package test
|
|
p contains 1 if {
|
|
v := input.x
|
|
}`,
|
|
"policy2.rego": `package test
|
|
p contains 2 if {
|
|
v := input.x
|
|
}`,
|
|
},
|
|
expErrs: []string{
|
|
"rego_parse_error: var cannot be used for rule name",
|
|
"rego_parse_error: number cannot be used for rule name",
|
|
},
|
|
},
|
|
}
|
|
|
|
bundleTypeCases := []struct {
|
|
note string
|
|
tar bool
|
|
}{
|
|
{
|
|
"bundle dir", false,
|
|
},
|
|
{
|
|
"bundle tar", true,
|
|
},
|
|
}
|
|
|
|
v1CompatibleFlagCases := []struct {
|
|
note string
|
|
used bool
|
|
}{
|
|
{
|
|
"no --v1-compatible", false,
|
|
},
|
|
{
|
|
"--v1-compatible", true,
|
|
},
|
|
}
|
|
|
|
for _, bundleType := range bundleTypeCases {
|
|
for _, v1CompatibleFlag := range v1CompatibleFlagCases {
|
|
for _, tc := range tests {
|
|
t.Run(fmt.Sprintf("%s, %s, %s", bundleType.note, v1CompatibleFlag.note, tc.note), func(t *testing.T) {
|
|
files := map[string]string{}
|
|
if bundleType.tar {
|
|
files["bundle.tar.gz"] = ""
|
|
} else {
|
|
maps.Copy(files, tc.files)
|
|
}
|
|
|
|
test.WithTempFS(files, func(root string) {
|
|
p := root
|
|
if bundleType.tar {
|
|
p = filepath.Join(root, "bundle.tar.gz")
|
|
files := make([][2]string, 0, len(tc.files))
|
|
for k, v := range tc.files {
|
|
files = append(files, [2]string{k, v})
|
|
}
|
|
buf := archive.MustWriteTarGz(files)
|
|
bf, err := os.Create(p)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
_, err = bf.Write(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
params := newInspectCommandParams()
|
|
params.v1Compatible = v1CompatibleFlag.used
|
|
err := params.outputFormat.Set(formats.Pretty)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
err = doInspect(params, p, &out)
|
|
|
|
if len(tc.expErrs) > 0 {
|
|
if err == nil {
|
|
t.Fatalf("Expected error but got output: %s", out.String())
|
|
}
|
|
|
|
for _, expErr := range tc.expErrs {
|
|
if !strings.Contains(err.Error(), expErr) {
|
|
t.Fatalf("Expected error:\n\n%v\n\nbut got:\n\n%v", expErr, err.Error())
|
|
}
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
expOut := fmt.Sprintf(`MANIFEST:
|
|
┌──────────────┬───────┐
|
|
│ FIELD │ VALUE │
|
|
├──────────────┼───────┤
|
|
│ Rego Version │ %d │
|
|
└──────────────┴───────┘`,
|
|
tc.bundleRegoVersion)
|
|
if !strings.Contains(out.String(), expOut) {
|
|
t.Fatalf("Expected output to contain:\n\n%s\n\nbut got:\n\n%s", expOut, out.String())
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUnknownRefs(t *testing.T) {
|
|
tests := []struct {
|
|
note string
|
|
files [][2]string
|
|
expected string
|
|
}{
|
|
{
|
|
note: "unknown built-in func call",
|
|
files: [][2]string{
|
|
{
|
|
"/policy.rego", `package test
|
|
p if {
|
|
foo.bar(42)
|
|
contains("foo", "o")
|
|
}`,
|
|
},
|
|
},
|
|
// Note: unknown foo.bar() built-in doesn't appear in the output, but also didn't cause an error.
|
|
expected: `{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/policy.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"builtins": [
|
|
{
|
|
"name": "contains",
|
|
"decl": {
|
|
"args": [
|
|
{
|
|
"type": "string"
|
|
},
|
|
{
|
|
"type": "string"
|
|
}
|
|
],
|
|
"result": {
|
|
"type": "boolean"
|
|
},
|
|
"type": "function"
|
|
}
|
|
}
|
|
],
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
// Happy path
|
|
note: "known ref replaced inside 'with' stmt",
|
|
files: [][2]string{
|
|
{"/policy.rego", `package test
|
|
|
|
foo.bar(_) := false
|
|
|
|
p if {
|
|
foo.bar(42)
|
|
}
|
|
|
|
mock(_) := true
|
|
|
|
test_p if {
|
|
p with data.test.foo.bar as mock
|
|
}`},
|
|
},
|
|
expected: `{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/policy.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
note: "unknown ref replaced inside 'with' stmt",
|
|
files: [][2]string{
|
|
{"/policy.rego", `package test
|
|
|
|
p if {
|
|
data.foo.bar(42)
|
|
}
|
|
|
|
mock(_) := true
|
|
|
|
test_p if {
|
|
p with data.foo.bar as mock
|
|
}`},
|
|
},
|
|
expected: `{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/policy.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
note: "unknown built-in (var) replaced inside 'with' stmt",
|
|
files: [][2]string{
|
|
{"/policy.rego", `package test
|
|
|
|
p if {
|
|
foo(42)
|
|
}
|
|
|
|
mock(_) := true
|
|
|
|
test_p if {
|
|
p with foo as mock
|
|
}`},
|
|
},
|
|
expected: `{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/policy.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
note: "unknown built-in (ref) replaced inside 'with' stmt",
|
|
files: [][2]string{
|
|
{"/policy.rego", `package test
|
|
|
|
p if {
|
|
foo.bar(42)
|
|
}
|
|
|
|
mock(_) := true
|
|
|
|
test_p if {
|
|
p with foo.bar as mock
|
|
}`},
|
|
},
|
|
expected: `{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/policy.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
note: "call replaced by unknown data ref inside 'with' stmt",
|
|
files: [][2]string{
|
|
{"/policy.rego", `package test
|
|
|
|
p if {
|
|
foo(42)
|
|
}
|
|
|
|
foo(_) := false
|
|
|
|
test_p if {
|
|
p with foo as data.bar
|
|
}`},
|
|
},
|
|
expected: `{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/policy.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"builtins": [
|
|
{
|
|
"name": "eq",
|
|
"decl": {
|
|
"args": [
|
|
{
|
|
"type": "any"
|
|
},
|
|
{
|
|
"type": "any"
|
|
}
|
|
],
|
|
"result": {
|
|
"type": "boolean"
|
|
},
|
|
"type": "function"
|
|
},
|
|
"infix": "="
|
|
}
|
|
],
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
note: "call replaced by unknown built-in (var) inside 'with' stmt",
|
|
files: [][2]string{
|
|
{"/policy.rego", `package test
|
|
|
|
p if {
|
|
foo(42)
|
|
}
|
|
|
|
foo(_) := false
|
|
|
|
test_p if {
|
|
# bar is unknown built-in
|
|
p with foo as bar
|
|
}`},
|
|
},
|
|
expected: `{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/policy.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`,
|
|
},
|
|
{
|
|
note: "call replaced by unknown built-in (ref) inside 'with' stmt",
|
|
files: [][2]string{
|
|
{"/policy.rego", `package test
|
|
|
|
p if {
|
|
foo(42)
|
|
}
|
|
|
|
foo(_) := false
|
|
|
|
test_p if {
|
|
# bar.baz is unknown built-in
|
|
p with foo as bar.baz
|
|
}`},
|
|
},
|
|
expected: `{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/policy.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"builtins": [
|
|
{
|
|
"name": "eq",
|
|
"decl": {
|
|
"args": [
|
|
{
|
|
"type": "any"
|
|
},
|
|
{
|
|
"type": "any"
|
|
}
|
|
],
|
|
"result": {
|
|
"type": "boolean"
|
|
},
|
|
"type": "function"
|
|
},
|
|
"infix": "="
|
|
}
|
|
],
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.note, func(t *testing.T) {
|
|
buf := archive.MustWriteTarGz(tc.files)
|
|
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
|
|
|
|
bf, err := os.Create(bundleFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
_, err = bf.Write(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
params := newInspectCommandParams()
|
|
err = params.outputFormat.Set(formats.JSON)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
err = doInspect(params, bundleFile, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
bs := out.Bytes()
|
|
output := strings.TrimSpace(string(bs))
|
|
if output != tc.expected {
|
|
t.Fatalf("Unexpected output. Expected:\n\n%s\n\nGot:\n\n%s", tc.expected, output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCallToUnknownRegoFunction(t *testing.T) {
|
|
files := [][2]string{
|
|
{"/policy.rego", `package test
|
|
import data.x.y
|
|
|
|
p if {
|
|
y(1) == true
|
|
}
|
|
`},
|
|
}
|
|
|
|
buf := archive.MustWriteTarGz(files)
|
|
bundleFile := filepath.Join(t.TempDir(), "bundle.tar.gz")
|
|
|
|
bf, err := os.Create(bundleFile)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
_, err = bf.Write(buf.Bytes())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var out bytes.Buffer
|
|
params := newInspectCommandParams()
|
|
err = params.outputFormat.Set(formats.JSON)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %s", err)
|
|
}
|
|
|
|
err = doInspect(params, bundleFile, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
bs := out.Bytes()
|
|
output := strings.TrimSpace(string(bs))
|
|
// Note: unknown data.x.y() function doesn't appear in the output, but also didn't cause an error.
|
|
expected := strings.TrimSpace(`{
|
|
"manifest": {
|
|
"revision": "",
|
|
"roots": [
|
|
""
|
|
]
|
|
},
|
|
"signatures_config": {},
|
|
"namespaces": {
|
|
"data.test": [
|
|
"/policy.rego"
|
|
]
|
|
},
|
|
"capabilities": {
|
|
"builtins": [
|
|
{
|
|
"name": "eq",
|
|
"decl": {
|
|
"args": [
|
|
{
|
|
"type": "any"
|
|
},
|
|
{
|
|
"type": "any"
|
|
}
|
|
],
|
|
"result": {
|
|
"type": "boolean"
|
|
},
|
|
"type": "function"
|
|
},
|
|
"infix": "="
|
|
},
|
|
{
|
|
"name": "equal",
|
|
"decl": {
|
|
"args": [
|
|
{
|
|
"type": "any"
|
|
},
|
|
{
|
|
"type": "any"
|
|
}
|
|
],
|
|
"result": {
|
|
"type": "boolean"
|
|
},
|
|
"type": "function"
|
|
},
|
|
"infix": "=="
|
|
}
|
|
],
|
|
"features": [
|
|
"rego_v1"
|
|
]
|
|
}
|
|
}`)
|
|
|
|
if output != expected {
|
|
t.Fatalf("Unexpected output. Expected:\n\n%s\n\nGot:\n\n%s", expected, output)
|
|
}
|
|
}
|
|
|
|
func TestDoInspectSingleFileWithAnnotations(t *testing.T) {
|
|
files := map[string]string{
|
|
"/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego": `# METADATA
|
|
# title: pkg-title
|
|
# description: pkg-descr
|
|
# organizations:
|
|
# - pkg-org
|
|
# related_resources:
|
|
# - https://pkg
|
|
# - ref: https://pkg
|
|
# description: rr-pkg-note
|
|
# authors:
|
|
# - pkg-author
|
|
# schemas:
|
|
# - input: {"type": "boolean"}
|
|
# custom:
|
|
# pkg: pkg-custom
|
|
package test
|
|
|
|
# METADATA
|
|
# scope: document
|
|
# title: doc-title
|
|
# description: doc-descr
|
|
# organizations:
|
|
# - doc-org
|
|
# related_resources:
|
|
# - https://doc
|
|
# - ref: https://doc
|
|
# description: rr-doc-note
|
|
# authors:
|
|
# - doc-author
|
|
# schemas:
|
|
# - input: {"type": "integer"}
|
|
# custom:
|
|
# doc: doc-custom
|
|
|
|
# METADATA
|
|
# title: rule-title
|
|
# description: rule-title
|
|
# organizations:
|
|
# - rule-org
|
|
# related_resources:
|
|
# - https://rule
|
|
# - ref: https://rule
|
|
# description: rr-rule-note
|
|
# authors:
|
|
# - rule-author
|
|
# schemas:
|
|
# - input: {"type": "string"}
|
|
# custom:
|
|
# rule: rule-custom
|
|
p = 1`,
|
|
}
|
|
|
|
test.WithTempFS(files, func(rootDir string) {
|
|
fileName := rootDir + "/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego"
|
|
ps := newInspectCommandParams()
|
|
ps.listAnnotations = true
|
|
var out bytes.Buffer
|
|
err := doInspect(ps, fileName, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
shortFileName := truncateFileName(fileName)
|
|
output := strings.TrimSpace(out.String())
|
|
expected := strings.TrimSpace(fmt.Sprintf(`
|
|
NAMESPACES:
|
|
┌───────────┬────────────────────────────────────────────────────┐
|
|
│ NAMESPACE │ FILE │
|
|
├───────────┼────────────────────────────────────────────────────┤
|
|
│ data.test │ %[1]s │
|
|
└───────────┴────────────────────────────────────────────────────┘
|
|
ANNOTATIONS:
|
|
pkg-title
|
|
=========
|
|
|
|
pkg-descr
|
|
|
|
Package: test
|
|
Location: %[2]s:16
|
|
Scope: package
|
|
|
|
Organizations:
|
|
pkg-org
|
|
|
|
Authors:
|
|
pkg-author
|
|
|
|
Schemas:
|
|
input: {"type":"boolean"}
|
|
|
|
Related Resources:
|
|
https://pkg
|
|
https://pkg rr-pkg-note
|
|
|
|
Custom:
|
|
pkg: "pkg-custom"
|
|
|
|
doc-title
|
|
=========
|
|
|
|
doc-descr
|
|
|
|
Package: test
|
|
Rule: p
|
|
Location: %[2]s:50
|
|
Scope: document
|
|
|
|
Organizations:
|
|
doc-org
|
|
|
|
Authors:
|
|
doc-author
|
|
|
|
Schemas:
|
|
input: {"type":"integer"}
|
|
|
|
Related Resources:
|
|
https://doc
|
|
https://doc rr-doc-note
|
|
|
|
Custom:
|
|
doc: "doc-custom"
|
|
|
|
rule-title
|
|
==========
|
|
|
|
rule-title
|
|
|
|
Package: test
|
|
Rule: p
|
|
Location: %[2]s:50
|
|
Scope: rule
|
|
|
|
Organizations:
|
|
rule-org
|
|
|
|
Authors:
|
|
rule-author
|
|
|
|
Schemas:
|
|
input: {"type":"string"}
|
|
|
|
Related Resources:
|
|
https://rule
|
|
https://rule rr-rule-note
|
|
|
|
Custom:
|
|
rule: "rule-custom"`, shortFileName, fileName))
|
|
|
|
if output != expected {
|
|
t.Fatalf("Unexpected output. Expected:\n\n%q\n\nGot:\n\n%q", expected, output)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDoInspectSingleFile(t *testing.T) {
|
|
files := map[string]string{
|
|
"/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego": `# METADATA
|
|
# title: pkg-title
|
|
# description: pkg-descr
|
|
# organizations:
|
|
# - pkg-org
|
|
# related_resources:
|
|
# - https://pkg
|
|
# - ref: https://pkg
|
|
# description: rr-pkg-note
|
|
# authors:
|
|
# - pkg-author
|
|
# schemas:
|
|
# - input: {"type": "boolean"}
|
|
# custom:
|
|
# pkg: pkg-custom
|
|
package test
|
|
|
|
# METADATA
|
|
# scope: document
|
|
# title: doc-title
|
|
# description: doc-descr
|
|
# organizations:
|
|
# - doc-org
|
|
# related_resources:
|
|
# - https://doc
|
|
# - ref: https://doc
|
|
# description: rr-doc-note
|
|
# authors:
|
|
# - doc-author
|
|
# schemas:
|
|
# - input: {"type": "integer"}
|
|
# custom:
|
|
# doc: doc-custom
|
|
|
|
# METADATA
|
|
# title: rule-title
|
|
# description: rule-title
|
|
# organizations:
|
|
# - rule-org
|
|
# related_resources:
|
|
# - https://rule
|
|
# - ref: https://rule
|
|
# description: rr-rule-note
|
|
# authors:
|
|
# - rule-author
|
|
# schemas:
|
|
# - input: {"type": "string"}
|
|
# custom:
|
|
# rule: rule-custom
|
|
p = 1`,
|
|
}
|
|
|
|
test.WithTempFS(files, func(rootDir string) {
|
|
fileName := rootDir + "/a/xxxxxxxxxxxxxxxxxxxxxx/yyyyyyyyyyyyyyyyyyyy/foo.rego"
|
|
ps := newInspectCommandParams()
|
|
var out bytes.Buffer
|
|
err := doInspect(ps, fileName, &out)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error %v", err)
|
|
}
|
|
|
|
shortFileName := truncateFileName(fileName)
|
|
output := strings.TrimSpace(out.String())
|
|
expected := strings.TrimSpace(fmt.Sprintf(`
|
|
NAMESPACES:
|
|
┌───────────┬────────────────────────────────────────────────────┐
|
|
│ NAMESPACE │ FILE │
|
|
├───────────┼────────────────────────────────────────────────────┤
|
|
│ data.test │ %v │
|
|
└───────────┴────────────────────────────────────────────────────┘
|
|
`, shortFileName))
|
|
|
|
if output != expected {
|
|
t.Fatalf("Unexpected output. Expected:\n%v\nGot:\n%v", expected, output)
|
|
}
|
|
})
|
|
}
|