diff --git a/ast/capabilities.go b/ast/capabilities.go index a6b162edf7..02fc3c277b 100644 --- a/ast/capabilities.go +++ b/ast/capabilities.go @@ -5,14 +5,19 @@ package ast import ( + "bytes" + "fmt" "io" + "os" "sort" + "strings" + caps "github.com/open-policy-agent/opa/capabilities" "github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities" "github.com/open-policy-agent/opa/util" ) -// Capabilities defines a structure containing data that describes the capablilities +// Capabilities defines a structure containing data that describes the capabilities // or features supported by a particular version of OPA. type Capabilities struct { Builtins []*Builtin `json:"builtins"` @@ -63,3 +68,48 @@ func LoadCapabilitiesJSON(r io.Reader) (*Capabilities, error) { var c Capabilities return &c, d.Decode(&c) } + +// LoadCapabilitiesVersion loads a JSON serialized capabilities structure from the specific version. +func LoadCapabilitiesVersion(version string) (*Capabilities, error) { + cvs, err := LoadCapabilitiesVersions() + if err != nil { + return nil, err + } + + for _, cv := range cvs { + if cv == version { + cont, err := caps.FS.ReadFile(cv + ".json") + if err != nil { + return nil, err + } + + return LoadCapabilitiesJSON(bytes.NewReader(cont)) + } + + } + return nil, fmt.Errorf("no capabilities version found %v", version) +} + +// LoadCapabilitiesFile loads a JSON serialized capabilities structure from a file. +func LoadCapabilitiesFile(file string) (*Capabilities, error) { + fd, err := os.Open(file) + if err != nil { + return nil, err + } + defer fd.Close() + return LoadCapabilitiesJSON(fd) +} + +// LoadCapabilitiesVersions loads all capabilities versions +func LoadCapabilitiesVersions() ([]string, error) { + ents, err := caps.FS.ReadDir(".") + if err != nil { + return nil, err + } + + var capabilitiesVersions []string + for _, ent := range ents { + capabilitiesVersions = append(capabilitiesVersions, strings.Replace(ent.Name(), ".json", "", 1)) + } + return capabilitiesVersions, nil +} diff --git a/ast/capabilities_test.go b/ast/capabilities_test.go index 0d4d8377da..d6db51f1a6 100644 --- a/ast/capabilities_test.go +++ b/ast/capabilities_test.go @@ -1,7 +1,10 @@ package ast import ( + "path" "testing" + + "github.com/open-policy-agent/opa/util/test" ) func TestParserCatchesIllegalCapabilities(t *testing.T) { @@ -83,3 +86,39 @@ func TestParserCapabilitiesWithWildcardOptInAndOlderOPA(t *testing.T) { t.Fatal("unexpected error:", err) } } + +func TestLoadCapabilitiesVersion(t *testing.T) { + + capabilitiesVersions, err := LoadCapabilitiesVersions() + if err != nil { + t.Fatal("expected success", err) + } + + if len(capabilitiesVersions) == 0 { + t.Fatal("expected a non-empty array of capabilities versions") + } + for _, cv := range capabilitiesVersions { + if _, err := LoadCapabilitiesVersion(cv); err != nil { + t.Fatal("expected success", err) + } + } +} + +func TestLoadCapabilitiesFile(t *testing.T) { + + files := map[string]string{ + "test-capabilities.json": ` + { + "builtins": [] + } + `, + } + + test.WithTempFS(files, func(root string) { + _, err := LoadCapabilitiesFile(path.Join(root, "test-capabilities.json")) + if err != nil { + t.Fatal("expected success", err) + } + }) + +} diff --git a/cmd/build.go b/cmd/build.go index 4e1fe5ff4e..b4bd96a6b2 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -201,7 +201,7 @@ The OPA repository contains a set of capabilities files for each OPA release. Fo the following command builds a directory of policies ('./policies') and validates them against OPA v0.22.0: - opa build ./policies --capabilities $OPA_SRC/capabilities/v0.22.0.json + opa build ./policies --capabilities v0.22.0 `, PreRunE: func(Cmd *cobra.Command, args []string) error { if len(args) == 0 { diff --git a/cmd/capabilities.go b/cmd/capabilities.go new file mode 100644 index 0000000000..8bd001d9de --- /dev/null +++ b/cmd/capabilities.go @@ -0,0 +1,124 @@ +// Copyright 2022 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package cmd + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/open-policy-agent/opa/ast" + "github.com/spf13/cobra" +) + +type capabilitiesParams struct { + showCurrent bool + version string + file string +} + +func init() { + + capabilitiesParams := capabilitiesParams{} + + var capabilitiesCommand = &cobra.Command{ + Use: "capabilities", + Short: "Print the capabilities of OPA", + Long: `Show capabilities for OPA. + +The 'capabilities' command prints the OPA capabilities, prior to and including the version of OPA used. + +Print a list of all existing capabilities version names + + $ opa capabilities + v0.17.0 + v0.17.1 + ... + v0.37.1 + v0.37.2 + v0.38.0 + ... + +Print the capabilities of the current version + + $ opa capabilities --current + { + "builtins": [...], + "future_keywords": [...], + "wasm_abi_versions": [...] + } + +Print the capabilities of a specific version + + $ opa capabilities --version v0.32.1 + { + "builtins": [...], + "future_keywords": null, + "wasm_abi_versions": [...] + } + +Print the capabilities of a capabilities file + + $ opa capabilities --file ./capabilities/v0.32.1.json + { + "builtins": [...], + "future_keywords": null, + "wasm_abi_versions": [...] + } + +`, + RunE: func(*cobra.Command, []string) error { + cs, err := doCapabilities(capabilitiesParams) + if err != nil { + return err + } + fmt.Println(cs) + return nil + }, + } + capabilitiesCommand.Flags().BoolVar(&capabilitiesParams.showCurrent, "current", false, "print current capabilities") + capabilitiesCommand.Flags().StringVar(&capabilitiesParams.version, "version", "", "print capabilities of a specific version") + capabilitiesCommand.Flags().StringVar(&capabilitiesParams.file, "file", "", "print current capabilities") + + RootCommand.AddCommand(capabilitiesCommand) +} + +func doCapabilities(params capabilitiesParams) (string, error) { + var ( + c *ast.Capabilities + err error + ) + + if len(params.version) > 0 { + c, err = ast.LoadCapabilitiesVersion(params.version) + } else if len(params.file) > 0 { + c, err = ast.LoadCapabilitiesFile(params.file) + } else if params.showCurrent { + c = ast.CapabilitiesForThisVersion() + } else { + return showVersions() + } + + if err != nil { + return "", err + } + + bs, err := json.MarshalIndent(c, "", " ") + if err != nil { + return "", err + } + return string(bs), nil + +} + +func showVersions() (string, error) { + cvs, err := ast.LoadCapabilitiesVersions() + if err != nil { + return "", err + } + + t := strings.Join(cvs, "\n") + return t, nil +} diff --git a/cmd/capabilities_test.go b/cmd/capabilities_test.go new file mode 100644 index 0000000000..6bfcdd75f1 --- /dev/null +++ b/cmd/capabilities_test.go @@ -0,0 +1,76 @@ +// Copyright 2022 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package cmd + +import ( + "path" + "testing" + + "github.com/open-policy-agent/opa/util/test" +) + +func TestCapabilitiesNoArgs(t *testing.T) { + t.Run("test with no arguments", func(t *testing.T) { + _, err := doCapabilities(capabilitiesParams{}) + if err != nil { + t.Fatal("expected success", err) + } + }) +} + +func TestCapabilitiesVersion(t *testing.T) { + t.Run("test with version", func(t *testing.T) { + params := capabilitiesParams{ + version: "v0.39.0", + } + _, err := doCapabilities(params) + if err != nil { + t.Fatal("expected success", err) + } + }) +} + +func TestCapabilitiesFile(t *testing.T) { + t.Run("test with file", func(t *testing.T) { + files := map[string]string{ + "test-capabilities.json": ` + { + "builtins": [ + { + "name": "plus", + "infix": "+", + "decl": { + "type": "function", + "args": [ + { + "type": "number" + }, + { + "type": "number" + } + ], + "result": { + "type": "number" + } + } + } + ] + } + `, + } + + test.WithTempFS(files, func(root string) { + params := capabilitiesParams{ + file: path.Join(root, "test-capabilities.json"), + } + _, err := doCapabilities(params) + + if err != nil { + t.Fatal("expected success", err) + } + }) + + }) +} diff --git a/cmd/flags.go b/cmd/flags.go index 330b317c74..47e5a12f05 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -6,7 +6,6 @@ package cmd import ( "fmt" - "os" "github.com/spf13/pflag" @@ -127,7 +126,7 @@ func addBundleVerificationExcludeFilesFlag(fs *pflag.FlagSet, excludeNames *[]st } func addCapabilitiesFlag(fs *pflag.FlagSet, f *capabilitiesFlag) { - fs.VarP(f, "capabilities", "", "set capabilities.json file path") + fs.VarP(f, "capabilities", "", "set capabilities version or capabilities.json file path") } func addPartialFlag(fs *pflag.FlagSet, partial *bool, value bool) { @@ -170,8 +169,8 @@ func setExplainFlag(fs *pflag.FlagSet, explain *util.EnumFlag) { } type capabilitiesFlag struct { - C *ast.Capabilities - path string + C *ast.Capabilities + pathOrVersion string } func newcapabilitiesFlag() *capabilitiesFlag { @@ -187,18 +186,23 @@ func (f *capabilitiesFlag) Type() string { } func (f *capabilitiesFlag) String() string { - return f.path + return f.pathOrVersion } func (f *capabilitiesFlag) Set(s string) error { - f.path = s - fd, err := os.Open(s) - if err != nil { - return err + f.pathOrVersion = s + var errPath, errVersion error + + f.C, errPath = ast.LoadCapabilitiesFile(s) + if errPath != nil { + f.C, errVersion = ast.LoadCapabilitiesVersion(s) } - defer fd.Close() - f.C, err = ast.LoadCapabilitiesJSON(fd) - return err + + if errVersion != nil && errPath != nil { + return fmt.Errorf("no such file or capabilities version found: %v", s) + } + return nil + } type stringptrFlag struct { diff --git a/docs/content/cli.md b/docs/content/cli.md index c40ec36f11..d772d81211 100755 --- a/docs/content/cli.md +++ b/docs/content/cli.md @@ -206,7 +206,7 @@ The OPA repository contains a set of capabilities files for each OPA release. Fo the following command builds a directory of policies ('./policies') and validates them against OPA v0.22.0: - opa build ./policies --capabilities $OPA_SRC/capabilities/v0.22.0.json + opa build ./policies --capabilities v0.22.0 ``` @@ -217,7 +217,7 @@ opa build [ [...]] [flags] ``` -b, --bundle load paths as bundle files or root directories - --capabilities string set capabilities.json file path + --capabilities string set capabilities version or capabilities.json file path --claims-file string set path of JSON file containing optional claims (see: https://www.openpolicyagent.org/docs/latest/management-bundles/#signature-format) --debug enable debug output -e, --entrypoint string set slash separated entrypoint path @@ -238,6 +238,71 @@ opa build [ [...]] [flags] ____ +## opa capabilities + +Print the capabilities of OPA + +### Synopsis + +Show capabilities for OPA. + +The 'capabilities' command prints the OPA capabilities, prior to and including the version of OPA used. + +Print a list of all existing capabilities version names + + $ opa capabilities + v0.17.0 + v0.17.1 + ... + v0.37.1 + v0.37.2 + v0.38.0 + ... + +Print the capabilities of the current version + + $ opa capabilities --current + { + "builtins": [...], + "future_keywords": [...], + "wasm_abi_versions": [...] + } + +Print the capabilities of a specific version + + $ opa capabilities --version v0.32.1 + { + "builtins": [...], + "future_keywords": null, + "wasm_abi_versions": [...] + } + +Print the capabilities of a capabilities file + + $ opa capabilities --file ./capabilities/v0.32.1.json + { + "builtins": [...], + "future_keywords": null, + "wasm_abi_versions": [...] + } + + + +``` +opa capabilities [flags] +``` + +### Options + +``` + --current print current capabilities + --file string print current capabilities + -h, --help help for capabilities + --version string print capabilities of a specific version +``` + +____ + ## opa check Check Rego source files @@ -258,7 +323,7 @@ opa check [path [...]] [flags] ``` -b, --bundle load paths as bundle files or root directories - --capabilities string set capabilities.json file path + --capabilities string set capabilities version or capabilities.json file path -f, --format {pretty,json} set output format (default pretty) -h, --help help for check --ignore strings set file and directory names to ignore during loading (e.g., '.*' excludes hidden files) @@ -435,7 +500,7 @@ opa eval [flags] ``` -b, --bundle string set bundle file(s) or directory path(s). This flag can be repeated. - --capabilities string set capabilities.json file path + --capabilities string set capabilities version or capabilities.json file path --count int number of times to repeat each benchmark (default 1) --coverage report coverage -d, --data string set policy or data file(s). This flag can be repeated.