//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) } }) }