cmd: Fix eval and fmt to support file:// URLs

Users should be able to pass file:// URLs to any of the
sub-commands. In 3be55ed6 the eval and fmt sub-commands were not
updated to accept file:// URLs for the input file and normal paths
(respectively).

This commit just moves the unexported cleanFileURL function from the
loader package into it's own internal package so that it can be shared
in OPA.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit is contained in:
Torin Sandall
2019-09-06 14:55:14 -04:00
parent 5418908e1c
commit 6a0fec7fb4
5 changed files with 117 additions and 40 deletions
+10 -5
View File
@@ -15,6 +15,7 @@ import (
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/cover"
fileurl "github.com/open-policy-agent/opa/internal/file/url"
pr "github.com/open-policy-agent/opa/internal/presentation"
"github.com/open-policy-agent/opa/internal/runtime"
"github.com/open-policy-agent/opa/metrics"
@@ -109,8 +110,8 @@ To evaluate a query against JSON data:
To evaluate a query against JSON data supplied with a file:// URL:
$ opa eval --data file:///path/to/file.json 'data'
File & Bundle Loading
---------------------
@@ -137,12 +138,12 @@ The JSON file 'foo/bar/data.json' would be loaded and rooted under
package path contained inside the file. Only data files named data.json or
data.yaml will be loaded. In the example above the manifest.yaml would be
ignored.
See https://www.openpolicyagent.org/docs/latest/bundles/ for more details
on bundle directory structures.
The --data flag can be used to recursively load ALL *.rego, *.json, and
*.yaml files under the specified directory.
*.yaml files under the specified directory.
Output Formats
--------------
@@ -421,7 +422,11 @@ func readInputBytes(params evalCommandParams) ([]byte, error) {
if params.stdinInput {
return ioutil.ReadAll(os.Stdin)
} else if params.inputPath != "" {
return ioutil.ReadFile(params.inputPath)
path, err := fileurl.Clean(params.inputPath)
if err != nil {
return nil, err
}
return ioutil.ReadFile(path)
}
return nil, nil
}
+9 -1
View File
@@ -14,7 +14,7 @@ import (
"path/filepath"
"github.com/open-policy-agent/opa/format"
fileurl "github.com/open-policy-agent/opa/internal/file/url"
"github.com/spf13/cobra"
)
@@ -59,6 +59,14 @@ func opaFmt(args []string) int {
}
for _, filename := range args {
var err error
filename, err = fileurl.Clean(filename)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}
if err := filepath.Walk(filename, formatFile); err != nil {
switch err := err.(type) {
case fmtError:
+42
View File
@@ -0,0 +1,42 @@
// Copyright 2019 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
// Package url contains helpers for dealing with file paths and URLs.
package url
import (
"fmt"
"net/url"
"runtime"
"strings"
)
var goos = runtime.GOOS
// Clean returns a cleaned file path that may or may not be a URL.
func Clean(path string) (string, error) {
if strings.Contains(path, "://") {
url, err := url.Parse(path)
if err != nil {
return "", err
}
if url.Scheme != "file" {
return "", fmt.Errorf("unsupported URL scheme: %v", path)
}
path = url.Path
// Trim leading slash on Windows if present. The url.Path field returned
// by url.Parse has leading slash that causes CreateFile() calls to fail
// on Windows. See https://github.com/golang/go/issues/6027 for details.
if goos == "windows" && len(path) >= 1 && path[0] == '/' {
path = path[1:]
}
}
return path, nil
}
+50
View File
@@ -0,0 +1,50 @@
package url
import "testing"
func TestClean(t *testing.T) {
cases := []struct {
input string
goos string
exp string
err error
}{
{
input: "c:/foo",
exp: "c:/foo",
goos: "windows",
},
{
input: "file:///c:/a/b",
exp: "c:/a/b",
goos: "windows",
},
{
input: "foo",
exp: "foo",
},
{
input: "/a/b/c",
exp: "/a/b/c",
},
{
input: "file:///a/b/c",
exp: "/a/b/c",
},
}
for _, tc := range cases {
t.Run(tc.input, func(t *testing.T) {
goos = tc.goos
path, err := Clean(tc.input)
if tc.err != nil {
if err == nil || err == tc.err {
t.Fatalf("Want err: %v but got: %v, err: %v", tc.err, path, err)
}
} else if err != nil || path != tc.exp {
t.Fatalf("Want %v but got: %v, err: %v", tc.exp, path, err)
}
})
}
}
+6 -34
View File
@@ -9,17 +9,15 @@ import (
"bytes"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/open-policy-agent/opa/internal/file"
"github.com/ghodss/yaml"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/bundle"
"github.com/open-policy-agent/opa/internal/file"
fileurl "github.com/open-policy-agent/opa/internal/file/url"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/storage/inmem"
"github.com/open-policy-agent/opa/util"
@@ -122,7 +120,7 @@ func Filtered(paths []string, filter Filter) (*Result, error) {
// Rego returns a RegoFile object loaded from the given path.
func Rego(path string) (*RegoFile, error) {
path, err := cleanFileURL(path)
path, err := fileurl.Clean(path)
if err != nil {
return nil, err
}
@@ -137,7 +135,7 @@ func Rego(path string) (*RegoFile, error) {
// it will be treated as a normal tarball bundle. If a directory
// is supplied it will be loaded as an unzipped bundle tree.
func AsBundle(path string) (*bundle.Bundle, error) {
path, err := cleanFileURL(path)
path, err := fileurl.Clean(path)
if err != nil {
return nil, err
}
@@ -173,7 +171,7 @@ func CleanPath(path string) string {
// and path is a directory, then Paths will walk the directory structure
// recursively and list files at each level.
func Paths(path string, recurse bool) (paths []string, err error) {
path, err = cleanFileURL(path)
path, err = fileurl.Clean(path)
if err != nil {
return nil, err
}
@@ -276,7 +274,7 @@ func all(paths []string, filter Filter, f func(*Result, string, int) error) (*Re
func allRec(path string, filter Filter, errors *loaderErrors, loaded *Result, depth int, f func(*Result, string, int) error) {
path, err := cleanFileURL(path)
path, err := fileurl.Clean(path)
if err != nil {
errors.Add(err)
return
@@ -316,32 +314,6 @@ func allRec(path string, filter Filter, errors *loaderErrors, loaded *Result, de
}
}
func cleanFileURL(path string) (string, error) {
if strings.Contains(path, "://") {
url, err := url.Parse(path)
if err != nil {
return "", err
}
if url.Scheme != "file" {
return "", fmt.Errorf("unsupported URL scheme: %v", path)
}
path = url.Path
// Trim leading slash on Windows if present. The url.Path field returned
// by url.Parse has leading slash that causes CreateFile() calls to fail
// on Windows. See https://github.com/golang/go/issues/6027 for details.
if runtime.GOOS == "windows" && len(path) >= 1 && path[0] == '/' {
path = path[1:]
}
}
return path, nil
}
func exclude(filters []Filter, path string, info os.FileInfo, depth int) bool {
for _, f := range filters {
if f(path, info, depth) {