From 9a51f45fd0870675cabeefeefc1a5198a15ef3ea Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Wed, 25 Sep 2024 17:53:40 +0100 Subject: [PATCH 01/14] feat: parse the annotations Signed-off-by: Alexandre Couedelo refactor: change the way we access annotations I am testing different way to generate the doc. Having the compiler object is much easier than the annotation set only Signed-off-by: Alexandre Couedelo --- document/document.md.tpl | 6 +++ document/metadata.go | 42 ++++++++++++++++++++ document/metadata_test.go | 59 ++++++++++++++++++++++++++++ document/testdata/doc/bar.md | 0 document/testdata/doc/foo.md | 7 ++++ document/testdata/foo/bar/bizz.rego | 8 ++++ document/testdata/foo/base.rego | 10 +++++ document/testdata/foo/base_test.rego | 5 +++ go.mod | 4 ++ 9 files changed, 141 insertions(+) create mode 100644 document/document.md.tpl create mode 100644 document/metadata.go create mode 100644 document/metadata_test.go create mode 100644 document/testdata/doc/bar.md create mode 100644 document/testdata/doc/foo.md create mode 100644 document/testdata/foo/bar/bizz.rego create mode 100644 document/testdata/foo/base.rego create mode 100644 document/testdata/foo/base_test.rego diff --git a/document/document.md.tpl b/document/document.md.tpl new file mode 100644 index 000000000..cfef2a896 --- /dev/null +++ b/document/document.md.tpl @@ -0,0 +1,6 @@ +# { .Title } + +{{ range .Rules }} +### {{ .Title }} + +{{end}} diff --git a/document/metadata.go b/document/metadata.go new file mode 100644 index 000000000..0cabbd315 --- /dev/null +++ b/document/metadata.go @@ -0,0 +1,42 @@ +package document + +import ( + "fmt" + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/loader" + "os" + "path/filepath" + "strings" +) + +// ParseRegoWithAnnotations parse the rego in the indicated directory +// This can be later used to access the annotation and generate the documentation +func ParseRegoWithAnnotations(directory string) (*ast.Compiler, error) { + // Recursively find all rego files (ignoring test files), starting at the given directory. + result, err := loader.NewFileLoader(). + WithProcessAnnotation(true). + Filtered([]string{directory}, func(_ string, info os.FileInfo, _ int) bool { + if strings.HasSuffix(info.Name(), "_test.rego") { + return true + } + + if !info.IsDir() && filepath.Ext(info.Name()) != ".rego" { + return true + } + + return false + }) + + if err != nil { + return nil, fmt.Errorf("filter rego files: %w", err) + } + + if _, err := result.Compiler(); err != nil { + return nil, fmt.Errorf("compile: %w", err) + } + + compiler := ast.NewCompiler() + compiler.Compile(result.ParsedModules()) + + return compiler, nil +} diff --git a/document/metadata_test.go b/document/metadata_test.go new file mode 100644 index 000000000..796546d42 --- /dev/null +++ b/document/metadata_test.go @@ -0,0 +1,59 @@ +package document + +import ( + "fmt" + "github.com/open-policy-agent/opa/ast" + "github.com/stretchr/testify/assert" + "testing" +) + +func validateAnnotation(t *testing.T, c *ast.Compiler, want []string) { + t.Helper() + + var got []string + + annotations := c.GetAnnotationSet().Flatten() + + for _, entry := range annotations { + got = append(got, fmt.Sprintf("%s/%s", entry.Annotations.Scope, entry.Annotations.Title)) + } + + assert.ElementsMatch(t, want, got) +} + +func TestGetAnnotations(t *testing.T) { + type args struct { + directory string + } + tests := []struct { + name string + args args + // list of scope/title of the annotation you expect to see + want []string + wantErr bool + }{ + { + name: "parse rule level metadata", + args: args{ + directory: "testdata/foo", + }, + want: []string{ + "subpackages/foo", + "package/bar", + "rule/My Rule A", + "rule/My Rule P", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseRegoWithAnnotations(tt.args.directory) + if (err != nil) != tt.wantErr { + t.Errorf("GetAnnotations() error = %v, wantErr %v", err, tt.wantErr) + return + } + validateAnnotation(t, got, tt.want) + }) + } +} diff --git a/document/testdata/doc/bar.md b/document/testdata/doc/bar.md new file mode 100644 index 000000000..e69de29bb diff --git a/document/testdata/doc/foo.md b/document/testdata/doc/foo.md new file mode 100644 index 000000000..1e760da3d --- /dev/null +++ b/document/testdata/doc/foo.md @@ -0,0 +1,7 @@ +# foo + +## Rule: My Rule A + +## Package: bar + +### Rule My Rule B diff --git a/document/testdata/foo/bar/bizz.rego b/document/testdata/foo/bar/bizz.rego new file mode 100644 index 000000000..50b298981 --- /dev/null +++ b/document/testdata/foo/bar/bizz.rego @@ -0,0 +1,8 @@ +# METADATA +# title: bar +# description: A couple of useful rules +package foo.bar + +# METADATA +# title: My Rule P +p := 7 diff --git a/document/testdata/foo/base.rego b/document/testdata/foo/base.rego new file mode 100644 index 000000000..506915acf --- /dev/null +++ b/document/testdata/foo/base.rego @@ -0,0 +1,10 @@ +# METADATA +# title: foo +# scope: subpackages +# organizations: +# - Acme Corp. +package foo + +# METADATA +# title: My Rule A +a := 3 diff --git a/document/testdata/foo/base_test.rego b/document/testdata/foo/base_test.rego new file mode 100644 index 000000000..12b12c4f2 --- /dev/null +++ b/document/testdata/foo/base_test.rego @@ -0,0 +1,5 @@ +package foo + +test_a_is_3 { + a == 3 +} diff --git a/go.mod b/go.mod index 1fc5a1922..a73b012d6 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/spdx/tools-golang v0.5.5 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.18.2 + github.com/stretchr/testify v1.9.0 github.com/subosito/gotenv v1.6.0 github.com/tmccombs/hcl2json v0.3.1 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 @@ -56,6 +57,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/containerd/typeurl/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -89,6 +91,8 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/prometheus/client_golang v1.20.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.20.4 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect From ca52b2ef2a7711db1907e76e6529638968d7b92c Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Thu, 26 Sep 2024 15:19:42 +0100 Subject: [PATCH 02/14] feat: generate section that can be used to generate documentations Signed-off-by: Alexandre Couedelo --- document/metadata.go | 39 ++++++++- document/metadata_test.go | 122 ++++++++++++++++++++++++++-- document/testdata/doc/bar.md | 13 +++ document/testdata/doc/foo.md | 10 ++- document/testdata/foo/bar/bizz.rego | 2 +- document/testdata/foo/base.rego | 2 +- 6 files changed, 173 insertions(+), 15 deletions(-) diff --git a/document/metadata.go b/document/metadata.go index 0cabbd315..b1fa40eb7 100644 --- a/document/metadata.go +++ b/document/metadata.go @@ -10,8 +10,7 @@ import ( ) // ParseRegoWithAnnotations parse the rego in the indicated directory -// This can be later used to access the annotation and generate the documentation -func ParseRegoWithAnnotations(directory string) (*ast.Compiler, error) { +func ParseRegoWithAnnotations(directory string) (ast.FlatAnnotationsRefSet, error) { // Recursively find all rego files (ignoring test files), starting at the given directory. result, err := loader.NewFileLoader(). WithProcessAnnotation(true). @@ -37,6 +36,40 @@ func ParseRegoWithAnnotations(directory string) (*ast.Compiler, error) { compiler := ast.NewCompiler() compiler.Compile(result.ParsedModules()) + as := compiler.GetAnnotationSet().Flatten() - return compiler, nil + return as, nil +} + +type Section struct { + H int + Path string + Annotation *ast.Annotations +} + +func (s Section) Equal(s2 Section) bool { + if s.H == s2.H && s.Path == s2.Path { + return true + } + + return false +} + +func GetDocument(as ast.FlatAnnotationsRefSet) []Section { + + var s []Section + + for _, entry := range as { + + depth := len(entry.Path) - 1 + path := strings.TrimPrefix(entry.Path.String(), "data.") + + s = append(s, Section{ + H: depth, + Path: path, + Annotation: entry.Annotations, + }) + } + + return s } diff --git a/document/metadata_test.go b/document/metadata_test.go index 796546d42..70730e97c 100644 --- a/document/metadata_test.go +++ b/document/metadata_test.go @@ -2,25 +2,58 @@ package document import ( "fmt" + "github.com/google/go-cmp/cmp" "github.com/open-policy-agent/opa/ast" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" ) -func validateAnnotation(t *testing.T, c *ast.Compiler, want []string) { +func validateAnnotation(t *testing.T, as ast.FlatAnnotationsRefSet, want []string) { t.Helper() var got []string - annotations := c.GetAnnotationSet().Flatten() - - for _, entry := range annotations { + for _, entry := range as { got = append(got, fmt.Sprintf("%s/%s", entry.Annotations.Scope, entry.Annotations.Title)) } assert.ElementsMatch(t, want, got) } +func getTestModules(t *testing.T, modules [][]string) ast.FlatAnnotationsRefSet { + t.Helper() + + parsed := make([]*ast.Module, 0, len(modules)) + for _, entry := range modules { + pm, err := ast.ParseModuleWithOpts(entry[0], entry[1], ast.ParserOptions{ProcessAnnotation: true}) + require.NoError(t, err) + parsed = append(parsed, pm) + } + + as, err := ast.BuildAnnotationSet(parsed) + require.Nil(t, err) + + return as.Flatten() +} + +// PartialEqual asserts that two objects are equal, depending on what equal means +// For instance, you may pass options to ignore certain fields +// Also if a struct export an Equal func this will be used for the assertion +func PartialEqual(t *testing.T, expected, actual any, diffOpts cmp.Option, msgAndArgs ...any) { + t.Helper() + + if cmp.Equal(expected, actual, diffOpts) { + return + } + + diff := cmp.Diff(expected, actual, diffOpts) + assert.Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expected, actual, diff), msgAndArgs...) + return +} + func TestGetAnnotations(t *testing.T) { type args struct { directory string @@ -38,8 +71,8 @@ func TestGetAnnotations(t *testing.T) { directory: "testdata/foo", }, want: []string{ - "subpackages/foo", - "package/bar", + "subpackages/My package foo", + "package/My package bar", "rule/My Rule A", "rule/My Rule P", }, @@ -57,3 +90,80 @@ func TestGetAnnotations(t *testing.T) { }) } } + +func TestGetDocument(t *testing.T) { + + tests := []struct { + name string + modules [][]string + want []Section + }{ + { + name: "Single file no package metadata", + modules: [][]string{ + {"foo.rego", ` +package foo + +# METADATA +# title: My Rule P +p := 7 +`}, + }, + want: []Section{ + { + H: 2, + Path: "foo.p", + }, + }, + }, + { + name: "Single file, multiple rule and package metadata", + modules: [][]string{ + {"foo.rego", ` +# METADATA +# title: My Package foo +package foo + +# METADATA +# title: My Rule P +p := 7 + +# METADATA +# title: My Rule Q +q := 8 +`}, + }, + want: []Section{ + { + H: 1, + Path: "foo", + Annotation: &ast.Annotations{ + Title: "My Rule P", + }, + }, + { + H: 2, + Path: "foo.p", + Annotation: &ast.Annotations{ + Title: "My Rule P", + }, + }, + { + H: 2, + Path: "foo.q", + Annotation: &ast.Annotations{ + Title: "My Rule Q", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := getTestModules(t, tt.modules) + got := GetDocument(m) + + PartialEqual(t, tt.want, got, nil) + }) + } +} diff --git a/document/testdata/doc/bar.md b/document/testdata/doc/bar.md index e69de29bb..84119a0ec 100644 --- a/document/testdata/doc/bar.md +++ b/document/testdata/doc/bar.md @@ -0,0 +1,13 @@ +# Policies - foo + +## Submodules + +* My package foo: [foo.bar](bar.md) + +## Rules + +### My Rule A + +```opa +a := 3 +``` diff --git a/document/testdata/doc/foo.md b/document/testdata/doc/foo.md index 1e760da3d..9c8c24673 100644 --- a/document/testdata/doc/foo.md +++ b/document/testdata/doc/foo.md @@ -1,7 +1,9 @@ -# foo +# Policies - bar -## Rule: My Rule A +## Rules -## Package: bar +### My Rule P -### Rule My Rule B +```opa +p := 7 +``` diff --git a/document/testdata/foo/bar/bizz.rego b/document/testdata/foo/bar/bizz.rego index 50b298981..844390cba 100644 --- a/document/testdata/foo/bar/bizz.rego +++ b/document/testdata/foo/bar/bizz.rego @@ -1,5 +1,5 @@ # METADATA -# title: bar +# title: My package bar # description: A couple of useful rules package foo.bar diff --git a/document/testdata/foo/base.rego b/document/testdata/foo/base.rego index 506915acf..dbe58629a 100644 --- a/document/testdata/foo/base.rego +++ b/document/testdata/foo/base.rego @@ -1,5 +1,5 @@ # METADATA -# title: foo +# title: My package foo # scope: subpackages # organizations: # - Acme Corp. From d2eab460116eb8fba62d78d19fc24fdaff224fac Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Thu, 26 Sep 2024 16:53:42 +0100 Subject: [PATCH 03/14] feat: generate the documentation file Signed-off-by: Alexandre Couedelo --- document/document.md.tpl | 6 ---- document/metadata.go | 19 ++++++----- document/metadata_test.go | 19 ++++++----- document/resources/document.md | 17 ++++++++++ document/template.go | 37 ++++++++++++++++++++++ document/template_test.go | 49 +++++++++++++++++++++++++++++ document/testdata/doc/bar.md | 13 -------- document/testdata/doc/foo.md | 26 +++++++++++---- document/testdata/foo/bar/bizz.rego | 3 +- document/testdata/foo/base.rego | 6 ++++ 10 files changed, 153 insertions(+), 42 deletions(-) delete mode 100644 document/document.md.tpl create mode 100644 document/resources/document.md create mode 100644 document/template.go create mode 100644 document/template_test.go delete mode 100644 document/testdata/doc/bar.md diff --git a/document/document.md.tpl b/document/document.md.tpl deleted file mode 100644 index cfef2a896..000000000 --- a/document/document.md.tpl +++ /dev/null @@ -1,6 +0,0 @@ -# { .Title } - -{{ range .Rules }} -### {{ .Title }} - -{{end}} diff --git a/document/metadata.go b/document/metadata.go index b1fa40eb7..e004ebf38 100644 --- a/document/metadata.go +++ b/document/metadata.go @@ -42,32 +42,35 @@ func ParseRegoWithAnnotations(directory string) (ast.FlatAnnotationsRefSet, erro } type Section struct { - H int - Path string - Annotation *ast.Annotations + H string + Path string + Annotations *ast.Annotations } func (s Section) Equal(s2 Section) bool { - if s.H == s2.H && s.Path == s2.Path { + if s.H == s2.H && + s.Path == s2.Path && + s.Annotations.Title == s2.Annotations.Title { return true } return false } +// GetDocument generate a more convenient struct that can be used to generate the doc func GetDocument(as ast.FlatAnnotationsRefSet) []Section { var s []Section for _, entry := range as { - depth := len(entry.Path) - 1 + depth := strings.Repeat("#", len(entry.Path)) path := strings.TrimPrefix(entry.Path.String(), "data.") s = append(s, Section{ - H: depth, - Path: path, - Annotation: entry.Annotations, + H: depth, + Path: path, + Annotations: entry.Annotations, }) } diff --git a/document/metadata_test.go b/document/metadata_test.go index 70730e97c..2393f62f4 100644 --- a/document/metadata_test.go +++ b/document/metadata_test.go @@ -111,8 +111,11 @@ p := 7 }, want: []Section{ { - H: 2, + H: "##", Path: "foo.p", + Annotations: &ast.Annotations{ + Title: "My Rule P", + }, }, }, }, @@ -135,23 +138,23 @@ q := 8 }, want: []Section{ { - H: 1, + H: "##", Path: "foo", - Annotation: &ast.Annotations{ - Title: "My Rule P", + Annotations: &ast.Annotations{ + Title: "My Package foo", }, }, { - H: 2, + H: "###", Path: "foo.p", - Annotation: &ast.Annotations{ + Annotations: &ast.Annotations{ Title: "My Rule P", }, }, { - H: 2, + H: "###", Path: "foo.q", - Annotation: &ast.Annotations{ + Annotations: &ast.Annotations{ Title: "My Rule Q", }, }, diff --git a/document/resources/document.md b/document/resources/document.md new file mode 100644 index 000000000..ca377044a --- /dev/null +++ b/document/resources/document.md @@ -0,0 +1,17 @@ +# Policies +{{ range . }} +{{ .H }} {{ .Path }} - {{ .Annotations.Title }} + +{{ .Annotations.Description }} +{{- if .Annotations.RelatedResources }} + +Related Resources: +{{ range .Annotations.RelatedResources }} +{{- if .Description -}} +* [{{.Description}}]({{ .Ref }}) +{{- else }} +* {{ .Ref }} +{{ end -}} +{{- end -}} +{{ end }} +{{ end }} diff --git a/document/template.go b/document/template.go new file mode 100644 index 000000000..9e7d0b62f --- /dev/null +++ b/document/template.go @@ -0,0 +1,37 @@ +package document + +import ( + "embed" + "io" + "io/fs" + "text/template" +) + +//go:embed resources/* +var resources embed.FS + +func generateDocument(out io.Writer, s []Section) error { + + err := renderTemplate(resources, "resources/document.md", s, out) + if err != nil { + return err + } + + return nil +} + +func renderTemplate(fs fs.FS, tpl string, args interface{}, out io.Writer) error { + // read the template + t, err := template.ParseFS(fs, tpl) + if err != nil { + return err + } + + // we render the template + err = t.Execute(out, args) + if err != nil { + return err + } + + return nil +} diff --git a/document/template_test.go b/document/template_test.go new file mode 100644 index 000000000..f29b765bc --- /dev/null +++ b/document/template_test.go @@ -0,0 +1,49 @@ +package document + +import ( + "bytes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "os" + "testing" +) + +func Test_generateDocument(t *testing.T) { + tests := []struct { + name string + testdata string + wantOut string + wantErr bool + }{ + { + name: "Nested packages", + testdata: "./testdata/foo", + wantOut: "./testdata/doc/foo.md", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + as, err := ParseRegoWithAnnotations(tt.testdata) + assert.NoError(t, err) + + s := GetDocument(as) + + gotOut := &bytes.Buffer{} + err = generateDocument(gotOut, s) + if (err != nil) != tt.wantErr { + t.Errorf("GenVariableDoc() error = %v, wantErr %v", err, tt.wantErr) + return + } + + wantOut, err := os.ReadFile(tt.wantOut) + require.NoError(t, err) + assert.Equal(t, string(wantOut), gotOut.String()) + + // un comment this to generate the golden file when changing the template + //os.WriteFile(tt.wantOut+".golden", gotOut.Bytes(), 0644) + }, + ) + } +} diff --git a/document/testdata/doc/bar.md b/document/testdata/doc/bar.md deleted file mode 100644 index 84119a0ec..000000000 --- a/document/testdata/doc/bar.md +++ /dev/null @@ -1,13 +0,0 @@ -# Policies - foo - -## Submodules - -* My package foo: [foo.bar](bar.md) - -## Rules - -### My Rule A - -```opa -a := 3 -``` diff --git a/document/testdata/doc/foo.md b/document/testdata/doc/foo.md index 9c8c24673..496784438 100644 --- a/document/testdata/doc/foo.md +++ b/document/testdata/doc/foo.md @@ -1,9 +1,23 @@ -# Policies - bar +# Policies -## Rules +## foo - My package foo -### My Rule P +the package with rule A and subpackage bar + +### foo.a - My Rule A + +the rule A = 3 + +Related Resources: + +* https://example.com +* [Yet another link](https://example.com/more) + +### foo.bar - My package bar + +The package with rule P + +#### foo.bar.p - My Rule P + +the Rule P = 7 -```opa -p := 7 -``` diff --git a/document/testdata/foo/bar/bizz.rego b/document/testdata/foo/bar/bizz.rego index 844390cba..dd3d82565 100644 --- a/document/testdata/foo/bar/bizz.rego +++ b/document/testdata/foo/bar/bizz.rego @@ -1,8 +1,9 @@ # METADATA # title: My package bar -# description: A couple of useful rules +# description: The package with rule P package foo.bar # METADATA # title: My Rule P +# description: the Rule P = 7 p := 7 diff --git a/document/testdata/foo/base.rego b/document/testdata/foo/base.rego index dbe58629a..ef6233dac 100644 --- a/document/testdata/foo/base.rego +++ b/document/testdata/foo/base.rego @@ -1,5 +1,6 @@ # METADATA # title: My package foo +# description: the package with rule A and subpackage bar # scope: subpackages # organizations: # - Acme Corp. @@ -7,4 +8,9 @@ package foo # METADATA # title: My Rule A +# description: the rule A = 3 +# related_resources: +# - ref: https://example.com +# - ref: https://example.com/more +# description: Yet another link a := 3 From b817229632833c64c9fa3a5ee2ce3bec701d9d67 Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Fri, 27 Sep 2024 12:44:31 +0100 Subject: [PATCH 04/14] feat: add the command doc to conftest Signed-off-by: Alexandre Couedelo fix: make title coherent in submodules documentation Signed-off-by: Alexandre Couedelo --- .gitignore | 3 + document/document.go | 29 ++++++++ document/metadata.go | 34 +++++++-- document/metadata_test.go | 106 +++++++++++++++++++++++++-- document/resources/document.md | 12 ++- document/template.go | 2 +- document/template_test.go | 7 +- document/testdata/doc/foo.md | 10 +-- internal/commands/default.go | 1 + internal/commands/document.go | 70 ++++++++++++++++++ tests/document/policy/base.rego | 25 +++++++ tests/document/policy/base_test.rego | 14 ++++ tests/document/policy/sub/bar.rego | 49 +++++++++++++ tests/document/test.bats | 19 +++++ 14 files changed, 353 insertions(+), 28 deletions(-) create mode 100644 document/document.go create mode 100644 internal/commands/document.go create mode 100644 tests/document/policy/base.rego create mode 100644 tests/document/policy/base_test.rego create mode 100644 tests/document/policy/sub/bar.rego create mode 100644 tests/document/test.bats diff --git a/.gitignore b/.gitignore index a8da11a32..6f76ecc75 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ site node_modules/ package.json package-lock.json + +# ignore generated doc in tests +/tests/document/*.md diff --git a/document/document.go b/document/document.go new file mode 100644 index 000000000..5333c11cb --- /dev/null +++ b/document/document.go @@ -0,0 +1,29 @@ +package document + +import ( + "fmt" + "io" +) + +// GenerateDocument generated a documentation file for a given module by parting +// A single page is generated for the module located in the indicated directory this includes the package subpackages +// and rules of the provided path, if you want to split the documentation. +func GenerateDocument(dir string, out io.Writer) error { + + as, err := ParseRegoWithAnnotations(dir) + if err != nil { + return fmt.Errorf("parse rego annotations: %w", err) + } + + sec, err := ConvertAnnotationsToSections(as) + if err != nil { + return fmt.Errorf("validating annotations: %w", err) + } + + err = RenderDocument(out, sec) + if err != nil { + return fmt.Errorf("rendering document: %w", err) + } + + return nil +} diff --git a/document/metadata.go b/document/metadata.go index e004ebf38..137946e7f 100644 --- a/document/metadata.go +++ b/document/metadata.go @@ -57,22 +57,44 @@ func (s Section) Equal(s2 Section) bool { return false } -// GetDocument generate a more convenient struct that can be used to generate the doc -func GetDocument(as ast.FlatAnnotationsRefSet) []Section { +// ConvertAnnotationsToSections generate a more convenient struct that can be used to generate the doc +// First concern is to build a coherent title structure, the ideal case is that each package and each rule as a doc, +// but this is not guarantied. I couldn't find a way to call strings.Repeat inside go-template, this the title key is +// directly provided as markdown (#, ##, ###, etc.) +// Second the attribute Path of ast.Annotations are not easy to used on go-template, thus we extract it as a string +func ConvertAnnotationsToSections(as ast.FlatAnnotationsRefSet) ([]Section, error) { var s []Section + var currentDepth = 0 + var offset = 1 - for _, entry := range as { + for i, entry := range as { + // offset at least by one because all path starts with `data.` + depth := len(entry.Path) - offset - depth := strings.Repeat("#", len(entry.Path)) + // If the user is targeting a submodule we need to adjust the depth an offset base on the first annotation found + if i == 0 && depth > 1 { + offset = depth + } + + // We need to compensate for unexpected jump in depth + // otherwise we would start at h3 if no package documentation is present + // or jump form h2 to h4 unexpectedly in subpackages + if (depth - currentDepth) > 1 { + depth = currentDepth + 1 + } + + currentDepth = depth + + h := strings.Repeat("#", depth) path := strings.TrimPrefix(entry.Path.String(), "data.") s = append(s, Section{ - H: depth, + H: h, Path: path, Annotations: entry.Annotations, }) } - return s + return s, nil } diff --git a/document/metadata_test.go b/document/metadata_test.go index 2393f62f4..3ae10f84a 100644 --- a/document/metadata_test.go +++ b/document/metadata_test.go @@ -97,11 +97,14 @@ func TestGetDocument(t *testing.T) { name string modules [][]string want []Section + wantErr bool }{ { - name: "Single file no package metadata", + name: "Single file", modules: [][]string{ {"foo.rego", ` +# METADATA +# title: My Package foo package foo # METADATA @@ -110,6 +113,13 @@ p := 7 `}, }, want: []Section{ + { + H: "#", + Path: "foo", + Annotations: &ast.Annotations{ + Title: "My Package foo", + }, + }, { H: "##", Path: "foo.p", @@ -119,6 +129,36 @@ p := 7 }, }, }, + { + name: "Single file of a subpackage", + modules: [][]string{ + {"foo/bar.rego", ` +# METADATA +# title: My Package bar +package foo.bar + +# METADATA +# title: My Rule P +p := 7 +`}, + }, + want: []Section{ + { + H: "#", + Path: "foo.bar", + Annotations: &ast.Annotations{ + Title: "My Package bar", + }, + }, + { + H: "##", + Path: "foo.bar.p", + Annotations: &ast.Annotations{ + Title: "My Rule P", + }, + }, + }, + }, { name: "Single file, multiple rule and package metadata", modules: [][]string{ @@ -138,33 +178,89 @@ q := 8 }, want: []Section{ { - H: "##", + H: "#", Path: "foo", Annotations: &ast.Annotations{ Title: "My Package foo", }, }, { - H: "###", + H: "##", Path: "foo.p", Annotations: &ast.Annotations{ Title: "My Rule P", }, }, { - H: "###", + H: "##", Path: "foo.q", Annotations: &ast.Annotations{ Title: "My Rule Q", }, }, }, + }, { + name: "Multiple file and subpackage", + modules: [][]string{ + {"foo.rego", ` +# METADATA +# title: My Package foo +package foo + +# METADATA +# title: My Rule P +p := 7 + +`}, + {"bar/bar.rego", ` +# METADATA +# title: My Package bar +package foo.bar + +# METADATA +# title: My Rule R +r := 9 + +`}, + }, + want: []Section{ + { + H: "#", + Path: "foo", + Annotations: &ast.Annotations{ + Title: "My Package foo", + }, + }, + { + H: "##", + Path: "foo.bar", + Annotations: &ast.Annotations{ + Title: "My Package bar", + }, + }, { + H: "###", + Path: "foo.bar.r", + Annotations: &ast.Annotations{ + Title: "My Rule R", + }, + }, { + H: "##", + Path: "foo.p", + Annotations: &ast.Annotations{ + Title: "My Rule P", + }, + }, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := getTestModules(t, tt.modules) - got := GetDocument(m) + got, err := ConvertAnnotationsToSections(m) + if (err != nil) != tt.wantErr { + t.Errorf("ConvertAnnotationsToSections() error = %v, wantErr %v", err, tt.wantErr) + return + } PartialEqual(t, tt.want, got, nil) }) diff --git a/document/resources/document.md b/document/resources/document.md index ca377044a..bfe4074ab 100644 --- a/document/resources/document.md +++ b/document/resources/document.md @@ -1,17 +1,15 @@ -# Policies -{{ range . }} +{{ range . -}} {{ .H }} {{ .Path }} - {{ .Annotations.Title }} {{ .Annotations.Description }} -{{- if .Annotations.RelatedResources }} - +{{ if .Annotations.RelatedResources }} Related Resources: {{ range .Annotations.RelatedResources }} -{{- if .Description -}} +{{ if .Description -}} * [{{.Description}}]({{ .Ref }}) -{{- else }} +{{- else -}} * {{ .Ref }} -{{ end -}} {{- end -}} {{ end }} {{ end }} +{{ end -}} diff --git a/document/template.go b/document/template.go index 9e7d0b62f..3c4de29e6 100644 --- a/document/template.go +++ b/document/template.go @@ -10,7 +10,7 @@ import ( //go:embed resources/* var resources embed.FS -func generateDocument(out io.Writer, s []Section) error { +func RenderDocument(out io.Writer, s []Section) error { err := renderTemplate(resources, "resources/document.md", s, out) if err != nil { diff --git a/document/template_test.go b/document/template_test.go index f29b765bc..79c2ccdcb 100644 --- a/document/template_test.go +++ b/document/template_test.go @@ -28,10 +28,11 @@ func Test_generateDocument(t *testing.T) { as, err := ParseRegoWithAnnotations(tt.testdata) assert.NoError(t, err) - s := GetDocument(as) + s, err := ConvertAnnotationsToSections(as) + assert.NoError(t, err) gotOut := &bytes.Buffer{} - err = generateDocument(gotOut, s) + err = RenderDocument(gotOut, s) if (err != nil) != tt.wantErr { t.Errorf("GenVariableDoc() error = %v, wantErr %v", err, tt.wantErr) return @@ -42,7 +43,7 @@ func Test_generateDocument(t *testing.T) { assert.Equal(t, string(wantOut), gotOut.String()) // un comment this to generate the golden file when changing the template - //os.WriteFile(tt.wantOut+".golden", gotOut.Bytes(), 0644) + os.WriteFile(tt.wantOut+".golden", gotOut.Bytes(), 0644) }, ) } diff --git a/document/testdata/doc/foo.md b/document/testdata/doc/foo.md index 496784438..3fed3fa97 100644 --- a/document/testdata/doc/foo.md +++ b/document/testdata/doc/foo.md @@ -1,10 +1,8 @@ -# Policies - -## foo - My package foo +# foo - My package foo the package with rule A and subpackage bar -### foo.a - My Rule A +## foo.a - My Rule A the rule A = 3 @@ -13,11 +11,11 @@ Related Resources: * https://example.com * [Yet another link](https://example.com/more) -### foo.bar - My package bar +## foo.bar - My package bar The package with rule P -#### foo.bar.p - My Rule P +### foo.bar.p - My Rule P the Rule P = 7 diff --git a/internal/commands/default.go b/internal/commands/default.go index ae077f923..11a8c5f02 100644 --- a/internal/commands/default.go +++ b/internal/commands/default.go @@ -60,6 +60,7 @@ func NewDefaultCommand() *cobra.Command { cmd.AddCommand(NewVerifyCommand(ctx)) cmd.AddCommand(NewPluginCommand(ctx)) cmd.AddCommand(NewFormatCommand()) + cmd.AddCommand(NewDocumentCommand()) plugins, err := plugin.FindAll() if err != nil { diff --git a/internal/commands/document.go b/internal/commands/document.go new file mode 100644 index 000000000..ab3608ab2 --- /dev/null +++ b/internal/commands/document.go @@ -0,0 +1,70 @@ +package commands + +import ( + "fmt" + "github.com/open-policy-agent/conftest/document" + "github.com/spf13/cobra" + "log" + "os" + "path/filepath" +) + +func NewDocumentCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "doc [path [...]]", + Short: "Generate documentation", + RunE: func(cmd *cobra.Command, dir []string) error { + if len(dir) < 1 { + err := cmd.Usage() + if err != nil { + return fmt.Errorf("usage: %s", err) + } + return fmt.Errorf("missing required arguments") + } + + for _, path := range dir { + // This returns an *os.FileInfo type + fileInfo, err := os.Stat(path) + if err != nil { + return err + } + + if !fileInfo.IsDir() { + return fmt.Errorf("%s is not a directory", path) + } + + // Handle the output destination + outDir, err := cmd.Flags().GetString("outDir") + if err != nil { + return fmt.Errorf("invalid outDir: %s", err) + } + + name := filepath.Base(path) + if name == "." { + name = "policy" + } + outPath := filepath.Join(outDir, name+".md") + f, err := os.OpenFile(outPath, os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return fmt.Errorf("opening %s for writing output: %w", outPath, err) + } + defer func(file *os.File) { + if err := file.Close(); err != nil { + log.Fatalln(err) + } + }(f) + + err = document.GenerateDocument(path, f) + if err != nil { + return fmt.Errorf("generating document: %w", err) + } + } + + return nil + }, + } + + cmd.Flags().StringP("outDir", "o", ".", "Path to the output documentation file") + + return cmd +} diff --git a/tests/document/policy/base.rego b/tests/document/policy/base.rego new file mode 100644 index 000000000..a00fa1184 --- /dev/null +++ b/tests/document/policy/base.rego @@ -0,0 +1,25 @@ +package main + +import data.services + +name := input.metadata.name +kind := input.kind +type := input.spec.type + +# METADATA +# title: Example using annotations +# description: This rule validates that ... +# custom: +# template: 'Cannot expose port %v on LoadBalancer. Denied ports: %v' +deny[msg] { + kind == "Service" + type == "LoadBalancer" + + some p + input.spec.ports[p].port + + input.spec.ports[p].port == services.ports[_] + + metadata := rego.metadata.rule() + msg := sprintf(metadata.custom.template, [input.spec.ports[p].port, services.ports]) +} diff --git a/tests/document/policy/base_test.rego b/tests/document/policy/base_test.rego new file mode 100644 index 000000000..1bafb44b6 --- /dev/null +++ b/tests/document/policy/base_test.rego @@ -0,0 +1,14 @@ +package main + +test_service_denied { + input := { + "kind": "Service", + "metadata": {"name": "sample"}, + "spec": { + "type": "LoadBalancer", + "ports": [{"port": 22}], + }, + } + + deny["Cannot expose port 22 on LoadBalancer. Denied ports: [22, 21]"] with input as input +} diff --git a/tests/document/policy/sub/bar.rego b/tests/document/policy/sub/bar.rego new file mode 100644 index 000000000..7a5ef8c19 --- /dev/null +++ b/tests/document/policy/sub/bar.rego @@ -0,0 +1,49 @@ +# METADATA +# title: Example using annotations +# description: This package validates that ... +# custom: +# template: 'Cannot expose port %v on LoadBalancer. Denied ports: %v' +package main.sub + +import data.services + +name := input.metadata.name +kind := input.kind +type := input.spec.type + +# METADATA +# title: Example using annotations +# description: This rule validates that ... +# custom: +# template: 'Cannot expose port %v on LoadBalancer. Denied ports: %v' +deny[msg] { + kind == "Service" + type == "LoadBalancer" + + some p + input.spec.ports[p].port + + input.spec.ports[p].port == services.ports[_] + + metadata := rego.metadata.rule() + msg := sprintf(metadata.custom.template, [input.spec.ports[p].port, services.ports]) +} + +# METADATA +# title: Second Example using annotations +# description: This rule validates that ... +# custom: +# template: 'Cannot expose port %v on LoadBalancer. Denied ports: %v' +deny[msg] { + kind == "Service" + type == "LoadBalancer" + + some p + input.spec.ports[p].port + + input.spec.ports[p].port == services.ports[_] + + metadata := rego.metadata.rule() + msg := sprintf(metadata.custom.template, [input.spec.ports[p].port, services.ports]) +} + diff --git a/tests/document/test.bats b/tests/document/test.bats new file mode 100644 index 000000000..719c4f1df --- /dev/null +++ b/tests/document/test.bats @@ -0,0 +1,19 @@ +#!/usr/bin/env bats + +@test "Can document the policies" { + rm "policy.md" + run $CONFTEST doc ./policy + + [ "$status" -eq 0 ] + echo $output + [ -f "policy.md" ] +} + +@test "Can document the sub package" { + rm "sub.md" + run $CONFTEST doc ./policy/sub + + [ "$status" -eq 0 ] + echo $output + [ -f "sub.md" ] +} From 0cf20bf38acd0092210d1f9b312d01ba8b127f11 Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Fri, 27 Sep 2024 13:36:22 +0100 Subject: [PATCH 05/14] chore: improve test case Signed-off-by: Alexandre Couedelo chore: ignore golden files in git Signed-off-by: Alexandre Couedelo chore: document and test the custom template feature Signed-off-by: Alexandre Couedelo chore: linting Signed-off-by: Alexandre Couedelo --- .gitignore | 2 + docs/documentation.md | 74 +++++++++++++++++++++++++++++++++ document/document.go | 9 +++- document/metadata.go | 5 ++- document/metadata_test.go | 4 +- document/template.go | 67 +++++++++++++++++++++++++---- document/template_test.go | 22 +++++++--- document/testdata/template.md | 15 +++++++ internal/commands/document.go | 17 +++++--- tests/document/policy/base.rego | 3 ++ tests/document/template.md.tpl | 3 ++ tests/document/test.bats | 15 ++++++- 12 files changed, 210 insertions(+), 26 deletions(-) create mode 100644 docs/documentation.md create mode 100644 document/testdata/template.md create mode 100644 tests/document/template.md.tpl diff --git a/.gitignore b/.gitignore index 6f76ecc75..e22f49dd5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ package-lock.json # ignore generated doc in tests /tests/document/*.md +# ignore prospective golden files +/document/testdata/doc/*.golden diff --git a/docs/documentation.md b/docs/documentation.md new file mode 100644 index 000000000..b9d93ef2c --- /dev/null +++ b/docs/documentation.md @@ -0,0 +1,74 @@ +# Generate Policy Documentations + +## Document your policies + +OPA has introduced a standard way to document policies called [Metadata](https://www.openpolicyagent.org/docs/latest/policy-language/#metadata). +This format allows for structured in code documentation of policies. + +```opa +# METADATA +# title: My rule +# description: A rule that determines if x is allowed. +# authors: +# - John Doe +# entrypoint: true +allow if { + ... +} +``` + +For the generated documentation to make sense your `packages` should be documented with at least the `title` field +and `rules` should have both `title` and `description`. This will ensure that no section is empty in your +documentations. + +## Generate the documentation + +In code documentation is great but what we often want it to later generated an actual static reference documentation. +The `doc` command will retrieve all annotation of a targeted module and generate a markdown documentation for it. + +```bash +conftest doc path/to/policy +``` + +## Use your own template + +You can override the [default template](../document/resources/document.md) with your own template + +```aiignore +conftest -t template.md path/tp/policies +``` + +All annotation are returned as a sorted list of all annotations, grouped by the path and location of their targeted +package or rule. For instance using this template + +```bash +{{ range . -}} +{{ .Path }} has annotations {{ .Annotations }} +{{ end -}} +``` + +for the following module + +```yaml +# METADATA +# scope: subpackages +# organizations: +# - Acme Corp. +package foo +--- +# METADATA +# description: A couple of useful rules +package foo.bar + +# METADATA +# title: My Rule P +p := 7 +``` + +You will obtain the following rendered documentation: + +```bash +data.foo has annotations {"organizations":["Acme Corp."],"scope":"subpackages"} +data.foo.bar has annotations {"description":"A couple of useful rules","scope":"package"} +data.foo.bar.p has annotations {"scope":"rule","title":"My Rule P"} +``` diff --git a/document/document.go b/document/document.go index 5333c11cb..7534f3f77 100644 --- a/document/document.go +++ b/document/document.go @@ -8,7 +8,7 @@ import ( // GenerateDocument generated a documentation file for a given module by parting // A single page is generated for the module located in the indicated directory this includes the package subpackages // and rules of the provided path, if you want to split the documentation. -func GenerateDocument(dir string, out io.Writer) error { +func GenerateDocument(dir string, tpl string, out io.Writer) error { as, err := ParseRegoWithAnnotations(dir) if err != nil { @@ -20,7 +20,12 @@ func GenerateDocument(dir string, out io.Writer) error { return fmt.Errorf("validating annotations: %w", err) } - err = RenderDocument(out, sec) + var opt []RenderDocumentOption + if tpl != "" { + opt = append(opt, WithTemplate(tpl)) + } + + err = RenderDocument(out, sec, opt...) if err != nil { return fmt.Errorf("rendering document: %w", err) } diff --git a/document/metadata.go b/document/metadata.go index 137946e7f..df2a9960d 100644 --- a/document/metadata.go +++ b/document/metadata.go @@ -2,11 +2,12 @@ package document import ( "fmt" - "github.com/open-policy-agent/opa/ast" - "github.com/open-policy-agent/opa/loader" "os" "path/filepath" "strings" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/loader" ) // ParseRegoWithAnnotations parse the rego in the indicated directory diff --git a/document/metadata_test.go b/document/metadata_test.go index 3ae10f84a..81c3ca17a 100644 --- a/document/metadata_test.go +++ b/document/metadata_test.go @@ -2,11 +2,12 @@ package document import ( "fmt" + "testing" + "github.com/google/go-cmp/cmp" "github.com/open-policy-agent/opa/ast" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) func validateAnnotation(t *testing.T, as ast.FlatAnnotationsRefSet, want []string) { @@ -51,7 +52,6 @@ func PartialEqual(t *testing.T, expected, actual any, diffOpts cmp.Option, msgAn assert.Fail(t, fmt.Sprintf("Not equal: \n"+ "expected: %s\n"+ "actual : %s%s", expected, actual, diff), msgAndArgs...) - return } func TestGetAnnotations(t *testing.T) { diff --git a/document/template.go b/document/template.go index 3c4de29e6..6d8012a5c 100644 --- a/document/template.go +++ b/document/template.go @@ -2,17 +2,55 @@ package document import ( "embed" + "fmt" "io" - "io/fs" "text/template" ) //go:embed resources/* var resources embed.FS -func RenderDocument(out io.Writer, s []Section) error { +// TemplateKind helps us to select where to find the template. It can either be embedded or on the host filesystem +type TemplateKind int - err := renderTemplate(resources, "resources/document.md", s, out) +const ( // iota is reset to 0 + FS TemplateKind = iota + FSYS // fsys is used for embedded templates +) + +type TemplateConfig struct { + kind TemplateKind + path string +} + +func NewTemplateConfig() *TemplateConfig { + return &TemplateConfig{ + kind: FSYS, + path: "resources/document.md", + } +} + +type RenderDocumentOption func(*TemplateConfig) + +// WithTemplate is a functional option to override the documentation template +func WithTemplate(tpl string) RenderDocumentOption { + return func(c *TemplateConfig) { + c.kind = FS + c.path = tpl + } +} + +// RenderDocument takes a slice of Section and generate the markdown documentation either using the default +// embedded template or the user provided template +func RenderDocument(out io.Writer, s []Section, opts ...RenderDocumentOption) error { + var tpl = NewTemplateConfig() + + // Apply all the functional options to the template configurations + for _, opt := range opts { + opt(tpl) + } + + err := renderTemplate(tpl, s, out) if err != nil { return err } @@ -20,11 +58,24 @@ func RenderDocument(out io.Writer, s []Section) error { return nil } -func renderTemplate(fs fs.FS, tpl string, args interface{}, out io.Writer) error { - // read the template - t, err := template.ParseFS(fs, tpl) - if err != nil { - return err +func renderTemplate(tpl *TemplateConfig, args interface{}, out io.Writer) error { + var t *template.Template + var err error + + switch tpl.kind { + case FSYS: + // read the embedded template + t, err = template.ParseFS(resources, tpl.path) + if err != nil { + return err + } + case FS: + t, err = template.ParseFiles(tpl.path) + if err != nil { + return err + } + default: + return fmt.Errorf("unknown template kind: %v", tpl.kind) } // we render the template diff --git a/document/template_test.go b/document/template_test.go index 79c2ccdcb..ade1a79fc 100644 --- a/document/template_test.go +++ b/document/template_test.go @@ -2,16 +2,18 @@ package document import ( "bytes" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "os" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_generateDocument(t *testing.T) { tests := []struct { name string testdata string + Option []RenderDocumentOption wantOut string wantErr bool }{ @@ -20,6 +22,14 @@ func Test_generateDocument(t *testing.T) { testdata: "./testdata/foo", wantOut: "./testdata/doc/foo.md", wantErr: false, + }, { + name: "Nested packages", + testdata: "./testdata/foo", + wantOut: "./testdata/doc/foo.md", + Option: []RenderDocumentOption{ + WithTemplate("testdata/template.md"), + }, + wantErr: false, }, } for _, tt := range tests { @@ -32,7 +42,7 @@ func Test_generateDocument(t *testing.T) { assert.NoError(t, err) gotOut := &bytes.Buffer{} - err = RenderDocument(gotOut, s) + err = RenderDocument(gotOut, s, tt.Option...) if (err != nil) != tt.wantErr { t.Errorf("GenVariableDoc() error = %v, wantErr %v", err, tt.wantErr) return @@ -42,8 +52,10 @@ func Test_generateDocument(t *testing.T) { require.NoError(t, err) assert.Equal(t, string(wantOut), gotOut.String()) - // un comment this to generate the golden file when changing the template - os.WriteFile(tt.wantOut+".golden", gotOut.Bytes(), 0644) + // prospective golden file, much simpler to see what's the result in case the test fails + // this does not override the existing test, but create a new file called xxx.golden + err = os.WriteFile(tt.wantOut+".golden", gotOut.Bytes(), 0600) + assert.NoError(t, err) }, ) } diff --git a/document/testdata/template.md b/document/testdata/template.md new file mode 100644 index 000000000..bfe4074ab --- /dev/null +++ b/document/testdata/template.md @@ -0,0 +1,15 @@ +{{ range . -}} +{{ .H }} {{ .Path }} - {{ .Annotations.Title }} + +{{ .Annotations.Description }} +{{ if .Annotations.RelatedResources }} +Related Resources: +{{ range .Annotations.RelatedResources }} +{{ if .Description -}} +* [{{.Description}}]({{ .Ref }}) +{{- else -}} +* {{ .Ref }} +{{- end -}} +{{ end }} +{{ end }} +{{ end -}} diff --git a/internal/commands/document.go b/internal/commands/document.go index ab3608ab2..16c2c375d 100644 --- a/internal/commands/document.go +++ b/internal/commands/document.go @@ -2,11 +2,12 @@ package commands import ( "fmt" - "github.com/open-policy-agent/conftest/document" - "github.com/spf13/cobra" "log" "os" "path/filepath" + + "github.com/open-policy-agent/conftest/document" + "github.com/spf13/cobra" ) func NewDocumentCommand() *cobra.Command { @@ -23,7 +24,6 @@ func NewDocumentCommand() *cobra.Command { } for _, path := range dir { - // This returns an *os.FileInfo type fileInfo, err := os.Stat(path) if err != nil { return err @@ -40,9 +40,10 @@ func NewDocumentCommand() *cobra.Command { } name := filepath.Base(path) - if name == "." { + if name == "." || name == ".." { name = "policy" } + outPath := filepath.Join(outDir, name+".md") f, err := os.OpenFile(outPath, os.O_CREATE|os.O_RDWR, 0600) if err != nil { @@ -54,7 +55,12 @@ func NewDocumentCommand() *cobra.Command { } }(f) - err = document.GenerateDocument(path, f) + template, err := cmd.Flags().GetString("template") + if err != nil { + return fmt.Errorf("invalid template: %s", err) + } + + err = document.GenerateDocument(path, template, f) if err != nil { return fmt.Errorf("generating document: %w", err) } @@ -65,6 +71,7 @@ func NewDocumentCommand() *cobra.Command { } cmd.Flags().StringP("outDir", "o", ".", "Path to the output documentation file") + cmd.Flags().StringP("template", "t", "", "Go template for the document generation") return cmd } diff --git a/tests/document/policy/base.rego b/tests/document/policy/base.rego index a00fa1184..7dd97aaa1 100644 --- a/tests/document/policy/base.rego +++ b/tests/document/policy/base.rego @@ -1,3 +1,6 @@ +# METADATA +# title: Example using annotations +# description: This package validates that ... package main import data.services diff --git a/tests/document/template.md.tpl b/tests/document/template.md.tpl new file mode 100644 index 000000000..d7c836c0f --- /dev/null +++ b/tests/document/template.md.tpl @@ -0,0 +1,3 @@ +{{ range . -}} +{{ .Path }} has annotations {{ .Annotations }} +{{ end -}} diff --git a/tests/document/test.bats b/tests/document/test.bats index 719c4f1df..b8eda85a6 100644 --- a/tests/document/test.bats +++ b/tests/document/test.bats @@ -1,7 +1,7 @@ #!/usr/bin/env bats @test "Can document the policies" { - rm "policy.md" + rm -f "policy.md" run $CONFTEST doc ./policy [ "$status" -eq 0 ] @@ -10,10 +10,21 @@ } @test "Can document the sub package" { - rm "sub.md" + rm -f "sub.md" run $CONFTEST doc ./policy/sub [ "$status" -eq 0 ] echo $output [ -f "sub.md" ] } + +@test "Can document using custom template and output" { + rm -f "custom/policy.md" + mkdir -p "custom" + run $CONFTEST doc -t ./template.md.tpl -o ./custom ./policy + + [ "$status" -eq 0 ] + echo $output + [ -f "custom/policy.md" ] +} + From 6a07b06676c04d31dc5006c4aa31a85f72807a2b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 20:31:42 -0700 Subject: [PATCH 06/14] build(deps): bump github.com/open-policy-agent/opa from 0.68.0 to 0.69.0 (#1010) Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 0.68.0 to 0.69.0. - [Release notes](https://github.com/open-policy-agent/opa/releases) - [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-policy-agent/opa/compare/v0.68.0...v0.69.0) --- updated-dependencies: - dependency-name: github.com/open-policy-agent/opa dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Alexandre Couedelo --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index a73b012d6..02b7da316 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( github.com/spdx/tools-golang v0.5.5 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.9.0 github.com/subosito/gotenv v1.6.0 github.com/tmccombs/hcl2json v0.3.1 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 From 9736e82e2984715401e6a8f470d5a90768017b7f Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Wed, 9 Oct 2024 09:38:28 +0000 Subject: [PATCH 07/14] chore: Fixed changes requested by @boranx build(deps): bump github.com/open-policy-agent/opa from 0.68.0 to 0.69.0 (#1010) Bumps [github.com/open-policy-agent/opa](https://github.com/open-policy-agent/opa) from 0.68.0 to 0.69.0. - [Release notes](https://github.com/open-policy-agent/opa/releases) - [Changelog](https://github.com/open-policy-agent/opa/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-policy-agent/opa/compare/v0.68.0...v0.69.0) --- updated-dependencies: - dependency-name: github.com/open-policy-agent/opa dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Alexandre Couedelo chore: go mod tidy --- document/document.go | 6 +++--- document/metadata.go | 27 ++++++++++++++++++++------- document/template.go | 13 +++++++++---- document/template_test.go | 4 ++-- go.mod | 1 + 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/document/document.go b/document/document.go index 7534f3f77..ae50e8e2d 100644 --- a/document/document.go +++ b/document/document.go @@ -5,9 +5,9 @@ import ( "io" ) -// GenerateDocument generated a documentation file for a given module by parting -// A single page is generated for the module located in the indicated directory this includes the package subpackages -// and rules of the provided path, if you want to split the documentation. +// GenerateDocument generate a documentation file for a given module +// A single page is generated for the module located in the indicated directory this includes all package, subpackages +// and rules of the provided path. func GenerateDocument(dir string, tpl string, out io.Writer) error { as, err := ParseRegoWithAnnotations(dir) diff --git a/document/metadata.go b/document/metadata.go index df2a9960d..56627b2a9 100644 --- a/document/metadata.go +++ b/document/metadata.go @@ -42,12 +42,24 @@ func ParseRegoWithAnnotations(directory string) (ast.FlatAnnotationsRefSet, erro return as, nil } +// Document represent a page of the documentation +type Document []Section + +// Section sequencial piece of documention comprise of ast.Annotations and some pre-processed fields +// This struct exist because some fields of ast.Annotations are not easy to manipulate in go-template type Section struct { - H string - Path string + // Path is the string representation of ast.Annotations.Path + Path string + // Depth represent title depth for this section (h1, h2, h3, etc.). This values is derived from len(ast.Annotations.Path) + // and smoothed such that subsequent section only defer by +/- 1 + Depth int + // H represent the markdown title symbol #, ##, ###, etc. (produced by strings.Repeat("#", depth)) + H string + // Annotations is the raw metada provided by OPA compiler Annotations *ast.Annotations } +// Equal is only relevant for test ans asset that two ection are partially Equal func (s Section) Equal(s2 Section) bool { if s.H == s2.H && s.Path == s2.Path && @@ -60,12 +72,12 @@ func (s Section) Equal(s2 Section) bool { // ConvertAnnotationsToSections generate a more convenient struct that can be used to generate the doc // First concern is to build a coherent title structure, the ideal case is that each package and each rule as a doc, -// but this is not guarantied. I couldn't find a way to call strings.Repeat inside go-template, this the title key is +// but this is not guaranteed. I couldn't find a way to call strings.Repeat inside go-template, thus the title symbol is // directly provided as markdown (#, ##, ###, etc.) // Second the attribute Path of ast.Annotations are not easy to used on go-template, thus we extract it as a string -func ConvertAnnotationsToSections(as ast.FlatAnnotationsRefSet) ([]Section, error) { +func ConvertAnnotationsToSections(as ast.FlatAnnotationsRefSet) (Document, error) { - var s []Section + var d Document var currentDepth = 0 var offset = 1 @@ -90,12 +102,13 @@ func ConvertAnnotationsToSections(as ast.FlatAnnotationsRefSet) ([]Section, erro h := strings.Repeat("#", depth) path := strings.TrimPrefix(entry.Path.String(), "data.") - s = append(s, Section{ + d = append(d, Section{ + Depth: depth, H: h, Path: path, Annotations: entry.Annotations, }) } - return s, nil + return d, nil } diff --git a/document/template.go b/document/template.go index 6d8012a5c..ff649f0a3 100644 --- a/document/template.go +++ b/document/template.go @@ -10,14 +10,16 @@ import ( //go:embed resources/* var resources embed.FS -// TemplateKind helps us to select where to find the template. It can either be embedded or on the host filesystem +// TemplateKind helps us to select where to find the template. +// It can either be embedded or on the host filesystem type TemplateKind int -const ( // iota is reset to 0 +const ( FS TemplateKind = iota FSYS // fsys is used for embedded templates ) +// TemplateConfig represent the location of the template file(s) type TemplateConfig struct { kind TemplateKind path string @@ -33,6 +35,7 @@ func NewTemplateConfig() *TemplateConfig { type RenderDocumentOption func(*TemplateConfig) // WithTemplate is a functional option to override the documentation template +// when overriding the template we assume it is located on the host file system func WithTemplate(tpl string) RenderDocumentOption { return func(c *TemplateConfig) { c.kind = FS @@ -42,7 +45,7 @@ func WithTemplate(tpl string) RenderDocumentOption { // RenderDocument takes a slice of Section and generate the markdown documentation either using the default // embedded template or the user provided template -func RenderDocument(out io.Writer, s []Section, opts ...RenderDocumentOption) error { +func RenderDocument(out io.Writer, d Document, opts ...RenderDocumentOption) error { var tpl = NewTemplateConfig() // Apply all the functional options to the template configurations @@ -50,7 +53,7 @@ func RenderDocument(out io.Writer, s []Section, opts ...RenderDocumentOption) er opt(tpl) } - err := renderTemplate(tpl, s, out) + err := renderTemplate(tpl, d, out) if err != nil { return err } @@ -58,6 +61,8 @@ func RenderDocument(out io.Writer, s []Section, opts ...RenderDocumentOption) er return nil } +// renderTemplate is an utility function to use go-template it handles fetching the template file(s) +// whether they are embeded or on the host file system. func renderTemplate(tpl *TemplateConfig, args interface{}, out io.Writer) error { var t *template.Template var err error diff --git a/document/template_test.go b/document/template_test.go index ade1a79fc..270f102f8 100644 --- a/document/template_test.go +++ b/document/template_test.go @@ -38,11 +38,11 @@ func Test_generateDocument(t *testing.T) { as, err := ParseRegoWithAnnotations(tt.testdata) assert.NoError(t, err) - s, err := ConvertAnnotationsToSections(as) + d, err := ConvertAnnotationsToSections(as) assert.NoError(t, err) gotOut := &bytes.Buffer{} - err = RenderDocument(gotOut, s, tt.Option...) + err = RenderDocument(gotOut, d, tt.Option...) if (err != nil) != tt.wantErr { t.Errorf("GenVariableDoc() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/go.mod b/go.mod index 02b7da316..a73b012d6 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/spdx/tools-golang v0.5.5 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.18.2 + github.com/stretchr/testify v1.9.0 github.com/subosito/gotenv v1.6.0 github.com/tmccombs/hcl2json v0.3.1 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 From dc4e66043aace3f57ff0d2456735e9a3ee09e4f5 Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Wed, 9 Oct 2024 13:36:16 +0100 Subject: [PATCH 08/14] test: refactor to use Document instead of []Section Signed-off-by: Alexandre Couedelo --- document/metadata_test.go | 10 +++++----- document/template.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/document/metadata_test.go b/document/metadata_test.go index 81c3ca17a..d54b03f02 100644 --- a/document/metadata_test.go +++ b/document/metadata_test.go @@ -96,7 +96,7 @@ func TestGetDocument(t *testing.T) { tests := []struct { name string modules [][]string - want []Section + want Document wantErr bool }{ { @@ -112,7 +112,7 @@ package foo p := 7 `}, }, - want: []Section{ + want: Document{ { H: "#", Path: "foo", @@ -142,7 +142,7 @@ package foo.bar p := 7 `}, }, - want: []Section{ + want: Document{ { H: "#", Path: "foo.bar", @@ -176,7 +176,7 @@ p := 7 q := 8 `}, }, - want: []Section{ + want: Document{ { H: "#", Path: "foo", @@ -223,7 +223,7 @@ r := 9 `}, }, - want: []Section{ + want: Document{ { H: "#", Path: "foo", diff --git a/document/template.go b/document/template.go index ff649f0a3..f271fe668 100644 --- a/document/template.go +++ b/document/template.go @@ -10,7 +10,7 @@ import ( //go:embed resources/* var resources embed.FS -// TemplateKind helps us to select where to find the template. +// TemplateKind helps us to select where to find the template. // It can either be embedded or on the host filesystem type TemplateKind int @@ -62,7 +62,7 @@ func RenderDocument(out io.Writer, d Document, opts ...RenderDocumentOption) err } // renderTemplate is an utility function to use go-template it handles fetching the template file(s) -// whether they are embeded or on the host file system. +// whether they are embedded or on the host file system. func renderTemplate(tpl *TemplateConfig, args interface{}, out io.Writer) error { var t *template.Template var err error From b91e99ff50c1a2f23f25a7843e9b2b0b8756220e Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Mon, 14 Oct 2024 10:28:43 +0100 Subject: [PATCH 09/14] chore: improve doc string in code Signed-off-by: Alexandre Couedelo --- document/metadata.go | 12 ++++++------ document/metadata_test.go | 2 +- document/template.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/document/metadata.go b/document/metadata.go index 56627b2a9..11e84e3fa 100644 --- a/document/metadata.go +++ b/document/metadata.go @@ -45,7 +45,7 @@ func ParseRegoWithAnnotations(directory string) (ast.FlatAnnotationsRefSet, erro // Document represent a page of the documentation type Document []Section -// Section sequencial piece of documention comprise of ast.Annotations and some pre-processed fields +// Section is a sequential piece of documentation comprised of ast.Annotations and some pre-processed fields // This struct exist because some fields of ast.Annotations are not easy to manipulate in go-template type Section struct { // Path is the string representation of ast.Annotations.Path @@ -59,7 +59,7 @@ type Section struct { Annotations *ast.Annotations } -// Equal is only relevant for test ans asset that two ection are partially Equal +// Equal is only relevant for tests and assert that two sections are partially Equal func (s Section) Equal(s2 Section) bool { if s.H == s2.H && s.Path == s2.Path && @@ -70,11 +70,11 @@ func (s Section) Equal(s2 Section) bool { return false } -// ConvertAnnotationsToSections generate a more convenient struct that can be used to generate the doc -// First concern is to build a coherent title structure, the ideal case is that each package and each rule as a doc, -// but this is not guaranteed. I couldn't find a way to call strings.Repeat inside go-template, thus the title symbol is +// ConvertAnnotationsToSections generates a more convenient struct that can be used to generate the doc +// First concern is to build a coherent title structure; the ideal case is that each package and each rule has a doc, +// but this is not guaranteed. I couldn't find a way to call `strings.Repeat` inside go-template; thus, the title symbol is // directly provided as markdown (#, ##, ###, etc.) -// Second the attribute Path of ast.Annotations are not easy to used on go-template, thus we extract it as a string +// Second, the attribute Path of ast.Annotations are not easy to use on go-template; thus, we extract it as a string func ConvertAnnotationsToSections(as ast.FlatAnnotationsRefSet) (Document, error) { var d Document diff --git a/document/metadata_test.go b/document/metadata_test.go index d54b03f02..fb472090a 100644 --- a/document/metadata_test.go +++ b/document/metadata_test.go @@ -40,7 +40,7 @@ func getTestModules(t *testing.T, modules [][]string) ast.FlatAnnotationsRefSet // PartialEqual asserts that two objects are equal, depending on what equal means // For instance, you may pass options to ignore certain fields -// Also if a struct export an Equal func this will be used for the assertion +// Also, if a struct exports an Equal func this will be used for the assertion func PartialEqual(t *testing.T, expected, actual any, diffOpts cmp.Option, msgAndArgs ...any) { t.Helper() diff --git a/document/template.go b/document/template.go index f271fe668..83d5ebfd4 100644 --- a/document/template.go +++ b/document/template.go @@ -35,7 +35,7 @@ func NewTemplateConfig() *TemplateConfig { type RenderDocumentOption func(*TemplateConfig) // WithTemplate is a functional option to override the documentation template -// when overriding the template we assume it is located on the host file system +// When overriding the template, we assume it is located on the host file system func WithTemplate(tpl string) RenderDocumentOption { return func(c *TemplateConfig) { c.kind = FS From 1c294f2b0ace3c85c8493c7986093a7b29081462 Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Thu, 24 Oct 2024 18:44:42 +0100 Subject: [PATCH 10/14] chore: address changes requested by @ jalseth Signed-off-by: Alexandre Couedelo --- document/document.go | 2 +- document/metadata.go | 41 ++++++----- document/metadata_test.go | 127 +++++++++++++++++---------------- document/resources/document.md | 2 +- document/template.go | 22 +++--- document/template_test.go | 63 +++++++++------- document/testdata/template.md | 2 +- internal/commands/document.go | 2 +- internal/commands/pull.go | 2 +- internal/commands/test.go | 4 +- internal/commands/verify.go | 4 +- 11 files changed, 144 insertions(+), 127 deletions(-) diff --git a/document/document.go b/document/document.go index ae50e8e2d..12f0a1b18 100644 --- a/document/document.go +++ b/document/document.go @@ -22,7 +22,7 @@ func GenerateDocument(dir string, tpl string, out io.Writer) error { var opt []RenderDocumentOption if tpl != "" { - opt = append(opt, WithTemplate(tpl)) + opt = append(opt, ExternalTemplate(tpl)) } err = RenderDocument(out, sec, opt...) diff --git a/document/metadata.go b/document/metadata.go index 11e84e3fa..cd5b95e99 100644 --- a/document/metadata.go +++ b/document/metadata.go @@ -1,6 +1,7 @@ package document import ( + "errors" "fmt" "os" "path/filepath" @@ -10,6 +11,10 @@ import ( "github.com/open-policy-agent/opa/loader" ) +var ( + ErrNoAnnotations = errors.New("no annotations found") +) + // ParseRegoWithAnnotations parse the rego in the indicated directory func ParseRegoWithAnnotations(directory string) (ast.FlatAnnotationsRefSet, error) { // Recursively find all rego files (ignoring test files), starting at the given directory. @@ -28,17 +33,19 @@ func ParseRegoWithAnnotations(directory string) (ast.FlatAnnotationsRefSet, erro }) if err != nil { - return nil, fmt.Errorf("filter rego files: %w", err) + return nil, fmt.Errorf("load rego files: %w", err) } - if _, err := result.Compiler(); err != nil { + compiler, err := result.Compiler() + if err != nil { return nil, fmt.Errorf("compile: %w", err) } - - compiler := ast.NewCompiler() - compiler.Compile(result.ParsedModules()) as := compiler.GetAnnotationSet().Flatten() + if len(as) == 0 { + return nil, ErrNoAnnotations + } + return as, nil } @@ -48,21 +55,21 @@ type Document []Section // Section is a sequential piece of documentation comprised of ast.Annotations and some pre-processed fields // This struct exist because some fields of ast.Annotations are not easy to manipulate in go-template type Section struct { - // Path is the string representation of ast.Annotations.Path - Path string - // Depth represent title depth for this section (h1, h2, h3, etc.). This values is derived from len(ast.Annotations.Path) + // RegoPackageName is the string representation of ast.Annotations.Path + RegoPackageName string + // Depth represent title depth for this section (h1, h2, h3, etc.). This values is derived from len(ast.Annotations.RegoPackageName) // and smoothed such that subsequent section only defer by +/- 1 Depth int - // H represent the markdown title symbol #, ##, ###, etc. (produced by strings.Repeat("#", depth)) - H string + // MarkdownHeading represent the markdown title symbol #, ##, ###, etc. (produced by strings.Repeat("#", depth)) + MarkdownHeading string // Annotations is the raw metada provided by OPA compiler Annotations *ast.Annotations } // Equal is only relevant for tests and assert that two sections are partially Equal func (s Section) Equal(s2 Section) bool { - if s.H == s2.H && - s.Path == s2.Path && + if s.MarkdownHeading == s2.MarkdownHeading && + s.RegoPackageName == s2.RegoPackageName && s.Annotations.Title == s2.Annotations.Title { return true } @@ -74,7 +81,7 @@ func (s Section) Equal(s2 Section) bool { // First concern is to build a coherent title structure; the ideal case is that each package and each rule has a doc, // but this is not guaranteed. I couldn't find a way to call `strings.Repeat` inside go-template; thus, the title symbol is // directly provided as markdown (#, ##, ###, etc.) -// Second, the attribute Path of ast.Annotations are not easy to use on go-template; thus, we extract it as a string +// Second, the attribute RegoPackageName of ast.Annotations are not easy to use on go-template; thus, we extract it as a string func ConvertAnnotationsToSections(as ast.FlatAnnotationsRefSet) (Document, error) { var d Document @@ -103,10 +110,10 @@ func ConvertAnnotationsToSections(as ast.FlatAnnotationsRefSet) (Document, error path := strings.TrimPrefix(entry.Path.String(), "data.") d = append(d, Section{ - Depth: depth, - H: h, - Path: path, - Annotations: entry.Annotations, + Depth: depth, + MarkdownHeading: h, + RegoPackageName: path, + Annotations: entry.Annotations, }) } diff --git a/document/metadata_test.go b/document/metadata_test.go index fb472090a..a200553fc 100644 --- a/document/metadata_test.go +++ b/document/metadata_test.go @@ -1,39 +1,29 @@ package document import ( - "fmt" + "errors" "testing" "github.com/google/go-cmp/cmp" "github.com/open-policy-agent/opa/ast" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func validateAnnotation(t *testing.T, as ast.FlatAnnotationsRefSet, want []string) { - t.Helper() - - var got []string - - for _, entry := range as { - got = append(got, fmt.Sprintf("%s/%s", entry.Annotations.Scope, entry.Annotations.Title)) - } - - assert.ElementsMatch(t, want, got) -} - func getTestModules(t *testing.T, modules [][]string) ast.FlatAnnotationsRefSet { t.Helper() parsed := make([]*ast.Module, 0, len(modules)) for _, entry := range modules { pm, err := ast.ParseModuleWithOpts(entry[0], entry[1], ast.ParserOptions{ProcessAnnotation: true}) - require.NoError(t, err) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } parsed = append(parsed, pm) } as, err := ast.BuildAnnotationSet(parsed) - require.Nil(t, err) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } return as.Flatten() } @@ -41,7 +31,7 @@ func getTestModules(t *testing.T, modules [][]string) ast.FlatAnnotationsRefSet // PartialEqual asserts that two objects are equal, depending on what equal means // For instance, you may pass options to ignore certain fields // Also, if a struct exports an Equal func this will be used for the assertion -func PartialEqual(t *testing.T, expected, actual any, diffOpts cmp.Option, msgAndArgs ...any) { +func PartialEqual(t *testing.T, expected, actual any, diffOpts cmp.Option) { t.Helper() if cmp.Equal(expected, actual, diffOpts) { @@ -49,50 +39,61 @@ func PartialEqual(t *testing.T, expected, actual any, diffOpts cmp.Option, msgAn } diff := cmp.Diff(expected, actual, diffOpts) - assert.Fail(t, fmt.Sprintf("Not equal: \n"+ + + t.Errorf("Not equal: \n"+ "expected: %s\n"+ - "actual : %s%s", expected, actual, diff), msgAndArgs...) + "actual : %s%s", expected, actual, diff) } -func TestGetAnnotations(t *testing.T) { - type args struct { - directory string - } +func TestParseRegoWithAnnotations(t *testing.T) { tests := []struct { - name string - args args + name string + directory string // list of scope/title of the annotation you expect to see want []string - wantErr bool + wantErr error }{ { - name: "parse rule level metadata", - args: args{ - directory: "testdata/foo", + name: "parse package and sub package", + directory: "testdata/foo", + want: []string{ + "data.foo", + "data.foo.a", + "data.foo.bar", + "data.foo.bar.p", }, + }, { + name: "target subpackage", + directory: "testdata/foo/bar", want: []string{ - "subpackages/My package foo", - "package/My package bar", - "rule/My Rule A", - "rule/My Rule P", + "data.foo.bar", + "data.foo.bar.p", }, - wantErr: false, + }, { + name: "target example awssam that as no annotations", + directory: "../examples/awssam", + want: []string{}, + wantErr: ErrNoAnnotations, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ParseRegoWithAnnotations(tt.args.directory) - if (err != nil) != tt.wantErr { - t.Errorf("GetAnnotations() error = %v, wantErr %v", err, tt.wantErr) + got, gotErr := ParseRegoWithAnnotations(tt.directory) + if !errors.Is(gotErr, tt.wantErr) { + t.Errorf("GetAnnotations() error = %v, wantErr %v", gotErr, tt.wantErr) return } - validateAnnotation(t, got, tt.want) + + for i, want := range tt.want { + if got[i].Path.String() != want { + t.Errorf("got[%d]Path.String() = %v, want %v", i, tt.want[i], got[i].Path.String()) + } + } }) } } -func TestGetDocument(t *testing.T) { - +func TestConvertAnnotationsToSection(t *testing.T) { tests := []struct { name string modules [][]string @@ -114,15 +115,15 @@ p := 7 }, want: Document{ { - H: "#", - Path: "foo", + MarkdownHeading: "#", + RegoPackageName: "foo", Annotations: &ast.Annotations{ Title: "My Package foo", }, }, { - H: "##", - Path: "foo.p", + MarkdownHeading: "##", + RegoPackageName: "foo.p", Annotations: &ast.Annotations{ Title: "My Rule P", }, @@ -144,15 +145,15 @@ p := 7 }, want: Document{ { - H: "#", - Path: "foo.bar", + MarkdownHeading: "#", + RegoPackageName: "foo.bar", Annotations: &ast.Annotations{ Title: "My Package bar", }, }, { - H: "##", - Path: "foo.bar.p", + MarkdownHeading: "##", + RegoPackageName: "foo.bar.p", Annotations: &ast.Annotations{ Title: "My Rule P", }, @@ -178,22 +179,22 @@ q := 8 }, want: Document{ { - H: "#", - Path: "foo", + MarkdownHeading: "#", + RegoPackageName: "foo", Annotations: &ast.Annotations{ Title: "My Package foo", }, }, { - H: "##", - Path: "foo.p", + MarkdownHeading: "##", + RegoPackageName: "foo.p", Annotations: &ast.Annotations{ Title: "My Rule P", }, }, { - H: "##", - Path: "foo.q", + MarkdownHeading: "##", + RegoPackageName: "foo.q", Annotations: &ast.Annotations{ Title: "My Rule Q", }, @@ -225,27 +226,27 @@ r := 9 }, want: Document{ { - H: "#", - Path: "foo", + MarkdownHeading: "#", + RegoPackageName: "foo", Annotations: &ast.Annotations{ Title: "My Package foo", }, }, { - H: "##", - Path: "foo.bar", + MarkdownHeading: "##", + RegoPackageName: "foo.bar", Annotations: &ast.Annotations{ Title: "My Package bar", }, }, { - H: "###", - Path: "foo.bar.r", + MarkdownHeading: "###", + RegoPackageName: "foo.bar.r", Annotations: &ast.Annotations{ Title: "My Rule R", }, }, { - H: "##", - Path: "foo.p", + MarkdownHeading: "##", + RegoPackageName: "foo.p", Annotations: &ast.Annotations{ Title: "My Rule P", }, diff --git a/document/resources/document.md b/document/resources/document.md index bfe4074ab..cf0a4f506 100644 --- a/document/resources/document.md +++ b/document/resources/document.md @@ -1,5 +1,5 @@ {{ range . -}} -{{ .H }} {{ .Path }} - {{ .Annotations.Title }} +{{ .MarkdownHeading }} {{ .RegoPackageName }} - {{ .Annotations.Title }} {{ .Annotations.Description }} {{ if .Annotations.RelatedResources }} diff --git a/document/template.go b/document/template.go index 83d5ebfd4..baad7d168 100644 --- a/document/template.go +++ b/document/template.go @@ -15,8 +15,8 @@ var resources embed.FS type TemplateKind int const ( - FS TemplateKind = iota - FSYS // fsys is used for embedded templates + TemplateKindInternal TemplateKind = iota + TemplateKindExternal ) // TemplateConfig represent the location of the template file(s) @@ -27,18 +27,18 @@ type TemplateConfig struct { func NewTemplateConfig() *TemplateConfig { return &TemplateConfig{ - kind: FSYS, + kind: TemplateKindInternal, path: "resources/document.md", } } type RenderDocumentOption func(*TemplateConfig) -// WithTemplate is a functional option to override the documentation template +// ExternalTemplate is a functional option to override the documentation template // When overriding the template, we assume it is located on the host file system -func WithTemplate(tpl string) RenderDocumentOption { +func ExternalTemplate(tpl string) RenderDocumentOption { return func(c *TemplateConfig) { - c.kind = FS + c.kind = TemplateKindExternal c.path = tpl } } @@ -46,7 +46,7 @@ func WithTemplate(tpl string) RenderDocumentOption { // RenderDocument takes a slice of Section and generate the markdown documentation either using the default // embedded template or the user provided template func RenderDocument(out io.Writer, d Document, opts ...RenderDocumentOption) error { - var tpl = NewTemplateConfig() + tpl := NewTemplateConfig() // Apply all the functional options to the template configurations for _, opt := range opts { @@ -63,18 +63,18 @@ func RenderDocument(out io.Writer, d Document, opts ...RenderDocumentOption) err // renderTemplate is an utility function to use go-template it handles fetching the template file(s) // whether they are embedded or on the host file system. -func renderTemplate(tpl *TemplateConfig, args interface{}, out io.Writer) error { +func renderTemplate(tpl *TemplateConfig, sections []Section, out io.Writer) error { var t *template.Template var err error switch tpl.kind { - case FSYS: + case TemplateKindInternal: // read the embedded template t, err = template.ParseFS(resources, tpl.path) if err != nil { return err } - case FS: + case TemplateKindExternal: t, err = template.ParseFiles(tpl.path) if err != nil { return err @@ -84,7 +84,7 @@ func renderTemplate(tpl *TemplateConfig, args interface{}, out io.Writer) error } // we render the template - err = t.Execute(out, args) + err = t.Execute(out, sections) if err != nil { return err } diff --git a/document/template_test.go b/document/template_test.go index 270f102f8..ff0a498cb 100644 --- a/document/template_test.go +++ b/document/template_test.go @@ -4,16 +4,13 @@ import ( "bytes" "os" "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func Test_generateDocument(t *testing.T) { +func TestRenderDocument(t *testing.T) { tests := []struct { name string testdata string - Option []RenderDocumentOption + Options []RenderDocumentOption wantOut string wantErr bool }{ @@ -26,37 +23,49 @@ func Test_generateDocument(t *testing.T) { name: "Nested packages", testdata: "./testdata/foo", wantOut: "./testdata/doc/foo.md", - Option: []RenderDocumentOption{ - WithTemplate("testdata/template.md"), + Options: []RenderDocumentOption{ + ExternalTemplate("testdata/template.md"), }, wantErr: false, }, } for _, tt := range tests { - t.Run( - tt.name, func(t *testing.T) { - as, err := ParseRegoWithAnnotations(tt.testdata) - assert.NoError(t, err) + t.Run(tt.name, func(t *testing.T) { + as, err := ParseRegoWithAnnotations(tt.testdata) + if (err != nil) != tt.wantErr { + t.Errorf("ParseRegoWithAnnotations() error = %v, wantErr %v", err, tt.wantErr) + } - d, err := ConvertAnnotationsToSections(as) - assert.NoError(t, err) + d, err := ConvertAnnotationsToSections(as) + if (err != nil) != tt.wantErr { + t.Errorf("ConvertAnnotationsToSections() error = %v, wantErr %v", err, tt.wantErr) + } - gotOut := &bytes.Buffer{} - err = RenderDocument(gotOut, d, tt.Option...) - if (err != nil) != tt.wantErr { - t.Errorf("GenVariableDoc() error = %v, wantErr %v", err, tt.wantErr) - return - } + gotOut := &bytes.Buffer{} + err = RenderDocument(gotOut, d, tt.Options...) + if (err != nil) != tt.wantErr { + t.Errorf("RenderDocument() error = %v, wantErr %v", err, tt.wantErr) + return + } - wantOut, err := os.ReadFile(tt.wantOut) - require.NoError(t, err) - assert.Equal(t, string(wantOut), gotOut.String()) + wantOut, err := os.ReadFile(tt.wantOut) + if err != nil { + t.Errorf("unexpected test error: %v", err) + return + } - // prospective golden file, much simpler to see what's the result in case the test fails - // this does not override the existing test, but create a new file called xxx.golden - err = os.WriteFile(tt.wantOut+".golden", gotOut.Bytes(), 0600) - assert.NoError(t, err) - }, + if gotOut.String() != string(wantOut) { + t.Errorf("ReadFile() = %v, want %v", gotOut.String(), wantOut) + } + + // prospective golden file, much simpler to see what's the result in case the test fails + // this does not override the existing test, but create a new file called xxx.golden + err = os.WriteFile(tt.wantOut+".golden", gotOut.Bytes(), 0600) + if err != nil { + t.Errorf("unexpected test error: %v", err) + return + } + }, ) } } diff --git a/document/testdata/template.md b/document/testdata/template.md index bfe4074ab..cf0a4f506 100644 --- a/document/testdata/template.md +++ b/document/testdata/template.md @@ -1,5 +1,5 @@ {{ range . -}} -{{ .H }} {{ .Path }} - {{ .Annotations.Title }} +{{ .MarkdownHeading }} {{ .RegoPackageName }} - {{ .Annotations.Title }} {{ .Annotations.Description }} {{ if .Annotations.RelatedResources }} diff --git a/internal/commands/document.go b/internal/commands/document.go index 16c2c375d..e1a55086e 100644 --- a/internal/commands/document.go +++ b/internal/commands/document.go @@ -70,7 +70,7 @@ func NewDocumentCommand() *cobra.Command { }, } - cmd.Flags().StringP("outDir", "o", ".", "Path to the output documentation file") + cmd.Flags().StringP("outDir", "o", ".", "RegoPackageName to the output documentation file") cmd.Flags().StringP("template", "t", "", "Go template for the document generation") return cmd diff --git a/internal/commands/pull.go b/internal/commands/pull.go index e8751247c..cef5eba51 100644 --- a/internal/commands/pull.go +++ b/internal/commands/pull.go @@ -76,7 +76,7 @@ func NewPullCommand(ctx context.Context) *cobra.Command { }, } - cmd.Flags().StringP("policy", "p", "policy", "Path to download the policies to") + cmd.Flags().StringP("policy", "p", "policy", "RegoPackageName to download the policies to") cmd.Flags().BoolP("tls", "s", true, "Use TLS to access the registry") return &cmd diff --git a/internal/commands/test.go b/internal/commands/test.go index d55ab62ae..313a8349e 100644 --- a/internal/commands/test.go +++ b/internal/commands/test.go @@ -175,12 +175,12 @@ func NewTestCommand(ctx context.Context) *cobra.Command { cmd.Flags().String("ignore", "", "A regex pattern which can be used for ignoring paths") cmd.Flags().String("parser", "", fmt.Sprintf("Parser to use to parse the configurations. Valid parsers: %s", parser.Parsers())) - cmd.Flags().String("capabilities", "", "Path to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed") + cmd.Flags().String("capabilities", "", "RegoPackageName to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed") cmd.Flags().StringP("output", "o", output.OutputStandard, fmt.Sprintf("Output format for conftest results - valid options are: %s", output.Outputs())) cmd.Flags().Bool("junit-hide-message", false, "Do not include the violation message in the JUnit test name") - cmd.Flags().StringSliceP("policy", "p", []string{"policy"}, "Path to the Rego policy files directory") + cmd.Flags().StringSliceP("policy", "p", []string{"policy"}, "RegoPackageName to the Rego policy files directory") cmd.Flags().StringSliceP("update", "u", []string{}, "A list of URLs can be provided to the update flag, which will download before the tests run") cmd.Flags().StringSliceP("namespace", "n", []string{"main"}, "Test policies in a specific namespace") cmd.Flags().StringSliceP("data", "d", []string{}, "A list of paths from which data for the rego policies will be recursively loaded") diff --git a/internal/commands/verify.go b/internal/commands/verify.go index 4bff3f96f..b7b647315 100644 --- a/internal/commands/verify.go +++ b/internal/commands/verify.go @@ -143,9 +143,9 @@ func NewVerifyCommand(ctx context.Context) *cobra.Command { cmd.Flags().StringP("output", "o", output.OutputStandard, fmt.Sprintf("Output format for conftest results - valid options are: %s", output.Outputs())) cmd.Flags().Bool("junit-hide-message", false, "Do not include the violation message in the JUnit test name") - cmd.Flags().String("capabilities", "", "Path to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed") + cmd.Flags().String("capabilities", "", "RegoPackageName to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed") cmd.Flags().StringSliceP("data", "d", []string{}, "A list of paths from which data for the rego policies will be recursively loaded") - cmd.Flags().StringSliceP("policy", "p", []string{"policy"}, "Path to the Rego policy files directory") + cmd.Flags().StringSliceP("policy", "p", []string{"policy"}, "RegoPackageName to the Rego policy files directory") cmd.Flags().StringSlice("proto-file-dirs", []string{}, "A list of directories containing Protocol Buffer definitions") From dbb40c4b1d7b9d7fbf3900029df3fddb97c90e6f Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Thu, 24 Oct 2024 18:49:02 +0100 Subject: [PATCH 11/14] chore: remove error handling on file close Signed-off-by: Alexandre Couedelo --- internal/commands/document.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/internal/commands/document.go b/internal/commands/document.go index e1a55086e..1b6b841f3 100644 --- a/internal/commands/document.go +++ b/internal/commands/document.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "log" "os" "path/filepath" @@ -49,11 +48,7 @@ func NewDocumentCommand() *cobra.Command { if err != nil { return fmt.Errorf("opening %s for writing output: %w", outPath, err) } - defer func(file *os.File) { - if err := file.Close(); err != nil { - log.Fatalln(err) - } - }(f) + defer f.Close() //nolint // CLI is exiting anyway and there's not much we can do. template, err := cmd.Flags().GetString("template") if err != nil { From 535f89e2e514a7d591e104bf19a171ac0e7754e8 Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Thu, 24 Oct 2024 18:57:07 +0100 Subject: [PATCH 12/14] fix: revert bad renaming Signed-off-by: Alexandre Couedelo --- go.mod | 1 - internal/commands/document.go | 2 +- internal/commands/pull.go | 2 +- internal/commands/test.go | 4 ++-- internal/commands/verify.go | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index a73b012d6..7c0676b9f 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,6 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/containerd/typeurl/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect diff --git a/internal/commands/document.go b/internal/commands/document.go index 1b6b841f3..92b48fd2a 100644 --- a/internal/commands/document.go +++ b/internal/commands/document.go @@ -65,7 +65,7 @@ func NewDocumentCommand() *cobra.Command { }, } - cmd.Flags().StringP("outDir", "o", ".", "RegoPackageName to the output documentation file") + cmd.Flags().StringP("outDir", "o", ".", "Path to the output documentation file") cmd.Flags().StringP("template", "t", "", "Go template for the document generation") return cmd diff --git a/internal/commands/pull.go b/internal/commands/pull.go index cef5eba51..e8751247c 100644 --- a/internal/commands/pull.go +++ b/internal/commands/pull.go @@ -76,7 +76,7 @@ func NewPullCommand(ctx context.Context) *cobra.Command { }, } - cmd.Flags().StringP("policy", "p", "policy", "RegoPackageName to download the policies to") + cmd.Flags().StringP("policy", "p", "policy", "Path to download the policies to") cmd.Flags().BoolP("tls", "s", true, "Use TLS to access the registry") return &cmd diff --git a/internal/commands/test.go b/internal/commands/test.go index 313a8349e..d55ab62ae 100644 --- a/internal/commands/test.go +++ b/internal/commands/test.go @@ -175,12 +175,12 @@ func NewTestCommand(ctx context.Context) *cobra.Command { cmd.Flags().String("ignore", "", "A regex pattern which can be used for ignoring paths") cmd.Flags().String("parser", "", fmt.Sprintf("Parser to use to parse the configurations. Valid parsers: %s", parser.Parsers())) - cmd.Flags().String("capabilities", "", "RegoPackageName to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed") + cmd.Flags().String("capabilities", "", "Path to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed") cmd.Flags().StringP("output", "o", output.OutputStandard, fmt.Sprintf("Output format for conftest results - valid options are: %s", output.Outputs())) cmd.Flags().Bool("junit-hide-message", false, "Do not include the violation message in the JUnit test name") - cmd.Flags().StringSliceP("policy", "p", []string{"policy"}, "RegoPackageName to the Rego policy files directory") + cmd.Flags().StringSliceP("policy", "p", []string{"policy"}, "Path to the Rego policy files directory") cmd.Flags().StringSliceP("update", "u", []string{}, "A list of URLs can be provided to the update flag, which will download before the tests run") cmd.Flags().StringSliceP("namespace", "n", []string{"main"}, "Test policies in a specific namespace") cmd.Flags().StringSliceP("data", "d", []string{}, "A list of paths from which data for the rego policies will be recursively loaded") diff --git a/internal/commands/verify.go b/internal/commands/verify.go index b7b647315..4bff3f96f 100644 --- a/internal/commands/verify.go +++ b/internal/commands/verify.go @@ -143,9 +143,9 @@ func NewVerifyCommand(ctx context.Context) *cobra.Command { cmd.Flags().StringP("output", "o", output.OutputStandard, fmt.Sprintf("Output format for conftest results - valid options are: %s", output.Outputs())) cmd.Flags().Bool("junit-hide-message", false, "Do not include the violation message in the JUnit test name") - cmd.Flags().String("capabilities", "", "RegoPackageName to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed") + cmd.Flags().String("capabilities", "", "Path to JSON file that can restrict opa functionality against a given policy. Default: all operations allowed") cmd.Flags().StringSliceP("data", "d", []string{}, "A list of paths from which data for the rego policies will be recursively loaded") - cmd.Flags().StringSliceP("policy", "p", []string{"policy"}, "RegoPackageName to the Rego policy files directory") + cmd.Flags().StringSliceP("policy", "p", []string{"policy"}, "Path to the Rego policy files directory") cmd.Flags().StringSlice("proto-file-dirs", []string{}, "A list of directories containing Protocol Buffer definitions") From 0396ea4a5c685ff675760c83d4410720ab59a6c2 Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Wed, 13 Nov 2024 12:22:01 +0000 Subject: [PATCH 13/14] fix: go mod tidy Signed-off-by: Alexandre Couedelo --- go.mod | 3 --- 1 file changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 7c0676b9f..1fc5a1922 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( github.com/spdx/tools-golang v0.5.5 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.9.0 github.com/subosito/gotenv v1.6.0 github.com/tmccombs/hcl2json v0.3.1 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 @@ -90,8 +89,6 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/prometheus/client_golang v1.20.5 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.20.4 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect From 998c2eb210be0a6971f06b23c19e14d7b797075f Mon Sep 17 00:00:00 2001 From: Alexandre Couedelo Date: Wed, 13 Nov 2024 14:23:06 +0000 Subject: [PATCH 14/14] fix: update template in acceptance test the name of the variable has changed to be more meaningful Signed-off-by: Alexandre Couedelo --- tests/document/template.md.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/document/template.md.tpl b/tests/document/template.md.tpl index d7c836c0f..e469e33e4 100644 --- a/tests/document/template.md.tpl +++ b/tests/document/template.md.tpl @@ -1,3 +1,3 @@ {{ range . -}} -{{ .Path }} has annotations {{ .Annotations }} +{{ .RegoPackageName }} has annotations {{ .Annotations }} {{ end -}}