mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
ast: extend capabilities to return min compatible version
This commit adds the ability to determine the minimum compatible OPA version for a set of capabilities. This can be coupled with the capabilities generated by the compiler to determine the min. compatible version of a policy/bundle. The build has been extended to generate the version index that lets us quickly check the required version for each builtin/feature/keyword in the capabilities. Signed-off-by: Torin Sandall <torin@styra.com>
This commit is contained in:
committed by
Torin Sandall
parent
3e6f747743
commit
16fccc9a76
@@ -6,6 +6,8 @@ package ast
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -13,10 +15,37 @@ import (
|
||||
"strings"
|
||||
|
||||
caps "github.com/open-policy-agent/opa/capabilities"
|
||||
"github.com/open-policy-agent/opa/internal/semver"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/sdk/opa/capabilities"
|
||||
"github.com/open-policy-agent/opa/util"
|
||||
)
|
||||
|
||||
// VersonIndex contains an index from built-in function name, language feature,
|
||||
// and future rego keyword to version number. During the build, this is used to
|
||||
// create an index of the minimum version required for the built-in/feature/kw.
|
||||
type VersionIndex struct {
|
||||
Builtins map[string]semver.Version `json:"builtins"`
|
||||
Features map[string]semver.Version `json:"features"`
|
||||
Keywords map[string]semver.Version `json:"keywords"`
|
||||
}
|
||||
|
||||
// NOTE(tsandall): this file is generated by internal/cmd/genversionindex/main.go
|
||||
// and run as part of go:generate. We generate the version index as part of the
|
||||
// build process because it's relatively expensive to build (it takes ~500ms on
|
||||
// my machine) and never changes.
|
||||
//
|
||||
//go:embed version_index.json
|
||||
var versionIndexBs []byte
|
||||
|
||||
var minVersionIndex = func() VersionIndex {
|
||||
var vi VersionIndex
|
||||
err := json.Unmarshal(versionIndexBs, &vi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return vi
|
||||
}()
|
||||
|
||||
// In the compiler, we used this to check that we're OK working with ref heads.
|
||||
// If this isn't present, we'll fail. This is to ensure that older versions of
|
||||
// OPA can work with policies that we're compiling -- if they don't know ref
|
||||
@@ -130,6 +159,50 @@ func LoadCapabilitiesVersions() ([]string, error) {
|
||||
return capabilitiesVersions, nil
|
||||
}
|
||||
|
||||
// MinimumCompatibleVersion returns the minimum compatible OPA version based on
|
||||
// the built-ins, features, and keywords in c.
|
||||
func (c *Capabilities) MinimumCompatibleVersion() (string, bool) {
|
||||
|
||||
var maxVersion semver.Version
|
||||
|
||||
// this is the oldest OPA release that includes capabilities
|
||||
if err := maxVersion.Set("0.17.0"); err != nil {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
for _, bi := range c.Builtins {
|
||||
v, ok := minVersionIndex.Builtins[bi.Name]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if v.Compare(maxVersion) > 0 {
|
||||
maxVersion = v
|
||||
}
|
||||
}
|
||||
|
||||
for _, kw := range c.FutureKeywords {
|
||||
v, ok := minVersionIndex.Keywords[kw]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if v.Compare(maxVersion) > 0 {
|
||||
maxVersion = v
|
||||
}
|
||||
}
|
||||
|
||||
for _, feat := range c.Features {
|
||||
v, ok := minVersionIndex.Features[feat]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if v.Compare(maxVersion) > 0 {
|
||||
maxVersion = v
|
||||
}
|
||||
}
|
||||
|
||||
return maxVersion.String(), true
|
||||
}
|
||||
|
||||
// addBuiltinSorted inserts a built-in into c in sorted order. An existing built-in with the same name
|
||||
// will be overwritten.
|
||||
func (c *Capabilities) addBuiltinSorted(bi *Builtin) {
|
||||
|
||||
@@ -157,6 +157,51 @@ func TestCapabilitiesAddBuiltinSorted(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapabilitiesMinimumCompatibleVersion(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
note string
|
||||
module string
|
||||
version string
|
||||
}{
|
||||
{
|
||||
note: "builtins",
|
||||
module: `
|
||||
package x
|
||||
p { array.reverse([1,2,3]) }
|
||||
`,
|
||||
version: "0.36.0",
|
||||
},
|
||||
{
|
||||
note: "keywords",
|
||||
module: `
|
||||
package x
|
||||
import future.keywords.every
|
||||
`,
|
||||
version: "0.38.0",
|
||||
},
|
||||
{
|
||||
note: "features",
|
||||
module: `
|
||||
package x
|
||||
import future.keywords.if
|
||||
p.a.b.c.d if { true }
|
||||
`,
|
||||
version: "0.46.0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.note, func(t *testing.T) {
|
||||
c := MustCompileModules(map[string]string{"test.rego": tc.module})
|
||||
minVersion, found := c.Required.MinimumCompatibleVersion()
|
||||
if !found || minVersion != tc.version {
|
||||
t.Fatal("expected", tc.version, "but got", minVersion)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func findBuiltinIndex(c *Capabilities, name string) int {
|
||||
for i, bi := range c.Builtins {
|
||||
if bi.Name == name {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1173,6 +1173,13 @@ func compile(c *ast.Capabilities, b *bundle.Bundle, dbg debug.Debug, enablePrint
|
||||
return nil, compiler.Errors
|
||||
}
|
||||
|
||||
minVersion, ok := compiler.Required.MinimumCompatibleVersion()
|
||||
if !ok {
|
||||
dbg.Printf("could not determine minimum compatible version!")
|
||||
} else {
|
||||
dbg.Printf("minimum compatible version: %v", minVersion)
|
||||
}
|
||||
|
||||
return compiler, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/open-policy-agent/opa/internal/semver"
|
||||
)
|
||||
|
||||
func minVersionIndex() ast.VersionIndex {
|
||||
|
||||
index := ast.VersionIndex{
|
||||
Builtins: map[string]semver.Version{},
|
||||
Features: map[string]semver.Version{},
|
||||
Keywords: map[string]semver.Version{},
|
||||
}
|
||||
|
||||
versions, err := ast.LoadCapabilitiesVersions()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, v := range versions {
|
||||
var sv semver.Version
|
||||
if err := sv.Set(v[1:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c, err := ast.LoadCapabilitiesVersion(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, bi := range c.Builtins {
|
||||
exist, ok := index.Builtins[bi.Name]
|
||||
if !ok || exist.Compare(sv) > 0 {
|
||||
index.Builtins[bi.Name] = sv
|
||||
}
|
||||
}
|
||||
|
||||
for _, kw := range c.FutureKeywords {
|
||||
exist, ok := index.Keywords[kw]
|
||||
if !ok || exist.Compare(sv) > 0 {
|
||||
index.Keywords[kw] = sv
|
||||
}
|
||||
}
|
||||
|
||||
for _, feat := range c.Features {
|
||||
exist, ok := index.Features[feat]
|
||||
if !ok || exist.Compare(sv) > 0 {
|
||||
index.Features[feat] = sv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return index
|
||||
}
|
||||
|
||||
func main() {
|
||||
fd, err := os.Create(os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(fd)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
vi := minVersionIndex()
|
||||
|
||||
if err := enc.Encode(vi); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := fd.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Capabilities + built-in metadata file generation:
|
||||
//go:generate build/gen-run-go.sh internal/cmd/genopacapabilities/main.go capabilities.json
|
||||
//go:generate build/gen-run-go.sh internal/cmd/genbuiltinmetadata/main.go builtin_metadata.json
|
||||
//go:generate build/gen-run-go.sh internal/cmd/genversionindex/main.go ast/version_index.json
|
||||
|
||||
Reference in New Issue
Block a user