diff --git a/pkg/doc/fragment.go b/pkg/doc/fragment.go index 313e527..1adc4ec 100644 --- a/pkg/doc/fragment.go +++ b/pkg/doc/fragment.go @@ -18,7 +18,6 @@ import ( "bytes" "errors" "fmt" - "io" "github.com/projectcontour/integration-tester/pkg/utils" @@ -79,6 +78,10 @@ func (t FragmentType) String() string { // Location tracks the lines that bound a Fragment within some larger Document. type Location struct { + // Filename is the name of the file this Fragment was read from + // (if that is known by the fragment reader). + Filename string + // Start is the line number this location starts on. Start int @@ -87,6 +90,10 @@ type Location struct { } func (l Location) String() string { + if l.Filename != "" { + return fmt.Sprintf("%s:%d-%d", l.Filename, l.Start, l.End) + } + return fmt.Sprintf("%d-%d", l.Start, l.End) } @@ -153,20 +160,6 @@ func decodeYAMLOrJSON(data []byte) (*unstructured.Unstructured, error) { return &unstructured.Unstructured{Object: into}, nil } -func decodeModule(data []byte) (*ast.Module, error) { - m, err := utils.ParseCheckFragment(string(data)) - if err != nil { - return nil, err - } - - // ParseModule can return nil with no error (empty module). - if m == nil { - return nil, io.EOF - } - - return m, nil -} - // IsDecoded returns whether this fragment has been decoded to a known fragment type. func (f *Fragment) IsDecoded() bool { switch f.Type { @@ -208,7 +201,7 @@ func (f *Fragment) Decode() (FragmentType, error) { // Since we do want to propagate errors so that users can debug // scripts, we have to assume this is meant to be Rego. - m, err := decodeModule(f.Bytes) + m, err := utils.ParseCheckFragment(f.Location.String(), string(f.Bytes)) if err != nil { return FragmentTypeInvalid, utils.ChainErrors( diff --git a/pkg/doc/read.go b/pkg/doc/read.go index 9f77610..96ed3e6 100644 --- a/pkg/doc/read.go +++ b/pkg/doc/read.go @@ -35,6 +35,7 @@ type Document struct { // YAML document separator (see https://yaml.org/spec/1.0/#id2561718). // The contents of each Fragment is opaque and need not be YAML. func ReadDocument(in io.Reader) (*Document, error) { + filename := "" startLine := 0 currentLine := 0 @@ -45,6 +46,10 @@ func ReadDocument(in io.Reader) (*Document, error) { scanner := bufio.NewScanner(in) + if f, ok := in.(*os.File); ok { + filename = f.Name() + } + // Scan the input a line at a time. for scanner.Scan() { currentLine++ @@ -66,8 +71,9 @@ func ReadDocument(in io.Reader) (*Document, error) { doc.Parts = append(doc.Parts, Fragment{ Bytes: utils.CopyBytes(buf.Bytes()), Location: Location{ - Start: startLine, - End: currentLine - 1, + Filename: filename, + Start: startLine, + End: currentLine - 1, }, }) } @@ -85,8 +91,9 @@ func ReadDocument(in io.Reader) (*Document, error) { doc.Parts = append(doc.Parts, Fragment{ Bytes: utils.CopyBytes(buf.Bytes()), Location: Location{ - Start: startLine, - End: currentLine, + Filename: filename, + Start: startLine, + End: currentLine, }, }) } diff --git a/pkg/utils/policy.go b/pkg/utils/policy.go index 391af2b..14346c0 100644 --- a/pkg/utils/policy.go +++ b/pkg/utils/policy.go @@ -43,15 +43,20 @@ func ParseModuleFile(filePath string) (*ast.Module, error) { // Rego input is assumed to not have a package declaration so a random // package name is prepended to make the parsed module globally unique. // ParseCheckFragment can return nil with no error if the input is empty. -func ParseCheckFragment(input string) (*ast.Module, error) { +// If the filename parameter is empty, an internal name will be generated. +func ParseCheckFragment(filename string, input string) (*ast.Module, error) { // Rego requires a package name to generate any Rules. Force // a package name that is unique to the fragment. Note that // we also use this to generate a unique filename placeholder // since Rego internals will sometime use this as a map key. moduleName := RandomStringN(12) + if filename == "" { + filename = fmt.Sprintf("internal/check/%s", moduleName) + } + m, err := ast.ParseModule( - fmt.Sprintf("internal/check/%s", moduleName), + filename, fmt.Sprintf("package check.%s\n%s", moduleName, input)) if err != nil { return nil, err