From 8e02a3075e56b3d29ce1e7b11602c6be90b633ac Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:48:01 +0100 Subject: [PATCH 1/2] default to ast.RegoV1 --- README.md | 2 +- example/go.sum | 2 ++ go.mod | 6 +++--- go.sum | 7 +++++-- options.go | 6 ++++++ runtime.go | 7 +------ runtime_test.go | 5 ++++- 7 files changed, 22 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index bb4d652..40de6a6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ go get -u github.com/aserto-dev/runtime ```go // Create a runtime -r, err := runtime.New(ctx, &logger, &runtime.Config{}) +r, err := runtime.New(ctx, &logger, &runtime.Config{}, runtime.WithRegoVersion(ast.RegoV1)) if err != nil { return errors.Wrap(err, "failed to create runtime") } diff --git a/example/go.sum b/example/go.sum index a2253f1..3bb0061 100644 --- a/example/go.sum +++ b/example/go.sum @@ -299,6 +299,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go.mod b/go.mod index 405e567..06bbf89 100644 --- a/go.mod +++ b/go.mod @@ -81,9 +81,9 @@ require ( golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect - google.golang.org/grpc v1.77.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect oras.land/oras-go/v2 v2.6.0 // indirect diff --git a/go.sum b/go.sum index 87e2fac..d8b9ab6 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/containerd/containerd/v2 v2.2.0 h1:K7TqcXy+LnFmZaui2DgHsnp2gAHhVNWYaHlx7HXfys8= github.com/containerd/containerd/v2 v2.2.0/go.mod h1:YCMjKjA4ZA7egdHNi3/93bJR1+2oniYlnS+c0N62HdE= -github.com/containerd/containerd/v2 v2.2.1 h1:TpyxcY4AL5A+07dxETevunVS5zxqzuq7ZqJXknM11yk= -github.com/containerd/containerd/v2 v2.2.1/go.mod h1:NR70yW1iDxe84F2iFWbR9xfAN0N2F0NcjTi1OVth4nU= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -259,10 +257,15 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E= +google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/options.go b/options.go index 7bc5221..6a84cc1 100644 --- a/options.go +++ b/options.go @@ -87,3 +87,9 @@ func WithImports(imp []string) Option { r.imports = append(r.imports, imp...) } } + +func WithRegoVersion(v ast.RegoVersion) Option { + return func(r *Runtime) { + r.regoVersion = v + } +} diff --git a/runtime.go b/runtime.go index 801e370..6dd484c 100644 --- a/runtime.go +++ b/runtime.go @@ -95,7 +95,7 @@ func New(ctx context.Context, cfg *Config, opts ...Option) (*Runtime, error) { pluginStates: &sync.Map{}, bundleStates: &sync.Map{}, plugins: map[string]plugins.Factory{}, - regoVersion: ast.RegoV0, + regoVersion: ast.RegoV1, } runtime.latestState.Store(&State{}) @@ -136,11 +136,6 @@ func New(ctx context.Context, cfg *Config, opts ...Option) (*Runtime, error) { return runtime, nil } -func (r *Runtime) WithRegoV1() *Runtime { - r.regoVersion = ast.RegoV1 - return r -} - // Start - triggers plugin manager to start all plugins. func (r *Runtime) Start(ctx context.Context) error { return r.pluginsManager.Start(ctx) diff --git a/runtime_test.go b/runtime_test.go index 749f751..0bf4e55 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -6,6 +6,7 @@ import ( runtime "github.com/aserto-dev/runtime" "github.com/aserto-dev/runtime/testutil" + "github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/plugins/bundle" "github.com/stretchr/testify/require" ) @@ -81,7 +82,9 @@ func TestRemoteBundle(t *testing.T) { }, }, }, - }) + }, + runtime.WithRegoVersion(ast.RegoV0), + ) assert.NoError(err) From 1ac44be2b1e5440ea86016e1d9c30c6742823ea4 Mon Sep 17 00:00:00 2001 From: Gert Drapers <1533850+gertd@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:40:28 +0100 Subject: [PATCH 2/2] use OPA path/file watcher implementation --- .golangci.yaml | 36 ++ example/go.mod | 22 +- example/go.sum | 19 +- go.mod | 18 + go.sum | 34 +- internal/file/archive/tarball.go | 76 ++++ internal/pathwatcher/export_test.go | 5 + internal/pathwatcher/utils.go | 147 ++++++++ internal/pathwatcher/utils_test.go | 107 ++++++ internal/runtime/init/init.go | 263 ++++++++++++++ internal/runtime/init/init_test.go | 532 ++++++++++++++++++++++++++++ internal/runtime/runtime.go | 60 ++++ internal/storage/mock/mock.go | 271 ++++++++++++++ internal/version/version.go | 35 ++ local_bundle_watcher.go | 258 +++----------- runtime.go | 14 +- 16 files changed, 1670 insertions(+), 227 deletions(-) create mode 100644 internal/file/archive/tarball.go create mode 100644 internal/pathwatcher/export_test.go create mode 100644 internal/pathwatcher/utils.go create mode 100644 internal/pathwatcher/utils_test.go create mode 100644 internal/runtime/init/init.go create mode 100644 internal/runtime/init/init_test.go create mode 100644 internal/runtime/runtime.go create mode 100644 internal/storage/mock/mock.go create mode 100644 internal/version/version.go diff --git a/.golangci.yaml b/.golangci.yaml index ccbde07..bb4ba7e 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -41,6 +41,7 @@ linters: - error - generic - logging.Logger + - storage.Store - storage.Transaction - storage.TriggerHandle @@ -68,6 +69,41 @@ linters: linters: - forbidigo + - path: internal/runtime/init/\w+\.go + linters: + - nonamedreturns + + - path: internal/file/archive/\w+\.go + linters: + - nonamedreturns + + - path: internal/pathwatcher/\w+\.go + linters: + - gosec + - nestif + + - path: internal/runtime/\w+\.go + linters: + - mnd + + - path: internal/runtime/init/\w+\.go + linters: + - funlen + - cyclop + - gocognit + - gosec + + - path: internal/file/archive/\w+\.go + linters: + - errcheck + - mnd + + - path: internal/storage/mock/\w+\.go + linters: + - err113 + - forcetypeassert + - ireturn + formatters: enable: - gofmt diff --git a/example/go.mod b/example/go.mod index 76310b0..5abd1b1 100644 --- a/example/go.mod +++ b/example/go.mod @@ -20,6 +20,7 @@ require ( github.com/aserto-dev/logger v0.0.9 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytecodealliance/wasmtime-go/v39 v39.0.1 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/containerd/v2 v2.2.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect @@ -27,6 +28,10 @@ require ( github.com/containerd/platforms v1.0.0-rc.2 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/dgraph-io/badger/v4 v4.8.0 // indirect + github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-ini/ini v1.67.0 // indirect @@ -35,7 +40,9 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect @@ -53,20 +60,26 @@ require ( github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/olekukonko/errors v1.1.0 // indirect + github.com/olekukonko/ll v0.0.9 // indirect + github.com/olekukonko/tablewriter v1.1.0 // indirect github.com/onsi/ginkgo/v2 v2.23.4 // indirect github.com/onsi/gomega v1.37.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/peterh/liner v1.2.2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect @@ -84,9 +97,14 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.46.0 // indirect @@ -95,7 +113,9 @@ require ( golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/grpc v1.77.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect + google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect oras.land/oras-go/v2 v2.6.0 // indirect diff --git a/example/go.sum b/example/go.sum index 3bb0061..51525a0 100644 --- a/example/go.sum +++ b/example/go.sum @@ -43,10 +43,12 @@ github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yh github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -75,6 +77,7 @@ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -135,6 +138,7 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -145,6 +149,9 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= @@ -157,11 +164,13 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -172,6 +181,7 @@ github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7D github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= @@ -293,12 +303,9 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= -google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/go.mod b/go.mod index 06bbf89..4458f94 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/agnivade/levenshtein v1.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytecodealliance/wasmtime-go/v39 v39.0.1 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/containerd/v2 v2.2.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect @@ -28,6 +29,10 @@ require ( github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/dgraph-io/badger/v4 v4.8.0 // indirect + github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -35,6 +40,8 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/flatbuffers v25.2.10+incompatible // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huandu/go-clone v1.7.3 // indirect @@ -51,16 +58,22 @@ require ( github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/olekukonko/errors v1.1.0 // indirect + github.com/olekukonko/ll v0.0.9 // indirect + github.com/olekukonko/tablewriter v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/peterh/liner v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.17.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/tchap/go-patricia/v2 v2.3.3 // indirect github.com/valyala/fastjson v1.6.4 // indirect @@ -71,9 +84,14 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/crypto v0.46.0 // indirect golang.org/x/net v0.48.0 // indirect diff --git a/go.sum b/go.sum index d8b9ab6..0698876 100644 --- a/go.sum +++ b/go.sum @@ -37,10 +37,14 @@ github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yh github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= +github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -63,6 +67,8 @@ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -119,6 +125,9 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -129,17 +138,27 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY= +github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= github.com/open-policy-agent/opa v1.12.1 h1:MWfmXuXB119O7rSOJ5GdKAaW15yBirjnLkFRBGy0EX0= github.com/open-policy-agent/opa v1.12.1/go.mod h1:RnDgm04GA1RjEXJvrsG9uNT/+FyBNmozcPvA2qz60M4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= +github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -150,6 +169,9 @@ github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7D github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= @@ -204,6 +226,8 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= @@ -233,6 +257,7 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -255,16 +280,13 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2 h1:7LRqPCEdE4TP4/9psdaB7F2nhZFfBiGJomA5sojLWdU= -google.golang.org/genproto/googleapis/api v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E= google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/internal/file/archive/tarball.go b/internal/file/archive/tarball.go new file mode 100644 index 0000000..ea36d11 --- /dev/null +++ b/internal/file/archive/tarball.go @@ -0,0 +1,76 @@ +package archive + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "encoding/json" + "errors" + "io" + "strings" +) + +type TarGzWriter struct { + *tar.Writer + + gw *gzip.Writer +} + +func NewTarGzWriter(w io.Writer) *TarGzWriter { + gw := gzip.NewWriter(w) + tw := tar.NewWriter(gw) + + return &TarGzWriter{ + Writer: tw, + gw: gw, + } +} + +func (tgw *TarGzWriter) WriteFile(path string, bs []byte) (err error) { + hdr := &tar.Header{ + Name: path, + Mode: 0o600, + Typeflag: tar.TypeReg, + Size: int64(len(bs)), + } + + if err = tgw.WriteHeader(hdr); err == nil { + _, err = tgw.Write(bs) + } + + return err +} + +func (tgw *TarGzWriter) WriteJSONFile(path string, v any) error { + buf := &bytes.Buffer{} + if err := json.NewEncoder(buf).Encode(v); err != nil { + return err + } + + return tgw.WriteFile(path, buf.Bytes()) +} + +func (tgw *TarGzWriter) Close() error { + return errors.Join(tgw.Writer.Close(), tgw.gw.Close()) +} + +// MustWriteTarGz writes the list of file names and content into a tarball. +// Paths are prefixed with "/". +func MustWriteTarGz(files [][2]string) *bytes.Buffer { + buf := &bytes.Buffer{} + + tgw := NewTarGzWriter(buf) + defer tgw.Close() + + for _, file := range files { + if !strings.HasPrefix(file[0], "/") { + file[0] = "/" + file[0] + } + + if err := tgw.WriteFile(file[0], []byte(file[1])); err != nil { + panic(err) + } + } + + return buf +} diff --git a/internal/pathwatcher/export_test.go b/internal/pathwatcher/export_test.go new file mode 100644 index 0000000..ee35e77 --- /dev/null +++ b/internal/pathwatcher/export_test.go @@ -0,0 +1,5 @@ +package pathwatcher + +func GetWatchPaths(rootPaths []string) ([]string, error) { + return getWatchPaths(rootPaths) +} diff --git a/internal/pathwatcher/utils.go b/internal/pathwatcher/utils.go new file mode 100644 index 0000000..6c74ae7 --- /dev/null +++ b/internal/pathwatcher/utils.go @@ -0,0 +1,147 @@ +// Copyright 2023 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 pathwatcher provides helper functions for creating file and directory watchers +package pathwatcher + +import ( + "context" + "os" + "path/filepath" + + initload "github.com/aserto-dev/runtime/internal/runtime/init" + + "github.com/fsnotify/fsnotify" + "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/loader" + "github.com/open-policy-agent/opa/v1/storage" + "github.com/open-policy-agent/opa/v1/util" +) + +// CreatePathWatcher creates watchers to monitor for path changes. +func CreatePathWatcher(rootPaths []string) (*fsnotify.Watcher, error) { + watchPaths, err := getWatchPaths(rootPaths) + if err != nil { + return nil, err + } + + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + + for _, path := range watchPaths { + if err := watcher.Add(path); err != nil { + return nil, err + } + } + + return watcher, nil +} + +// ProcessWatcherUpdate handles an occurrence of a watcher event. +func ProcessWatcherUpdate( + ctx context.Context, + paths []string, + removed string, + store storage.Store, + filter loader.Filter, + asBundle bool, + bundleLazyLoadingMode bool, + f func(context.Context, storage.Transaction, *initload.LoadPathsResult) error, +) error { + return ProcessWatcherUpdateForRegoVersion(ctx, ast.DefaultRegoVersion, paths, removed, store, filter, asBundle, bundleLazyLoadingMode, f) +} + +func ProcessWatcherUpdateForRegoVersion( + ctx context.Context, + regoVersion ast.RegoVersion, + paths []string, + removed string, + store storage.Store, + filter loader.Filter, + asBundle bool, + bundleLazyLoadingMode bool, + f func(context.Context, storage.Transaction, *initload.LoadPathsResult) error, +) error { + loaded, err := initload.LoadPathsForRegoVersion(regoVersion, paths, filter, asBundle, nil, true, bundleLazyLoadingMode, false, false, nil, nil) + if err != nil { + return err + } + + removed = loader.CleanPath(removed) + + return storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error { + if !asBundle { + ids, err := store.ListPolicies(ctx, txn) + if err != nil { + return err + } + + for _, id := range ids { + if id == removed { + if err := store.DeletePolicy(ctx, txn, id); err != nil { + return err + } + } else if _, exists := loaded.Files.Modules[id]; !exists { + // This branch get hit in two cases. + // 1. Another piece of code has access to the store and inserts + // a policy out-of-band. + // 2. In between FS notification and loader.Filtered() call above, a + // policy is removed from disk. + bs, err := store.GetPolicy(ctx, txn, id) + if err != nil { + return err + } + + module, err := ast.ParseModuleWithOpts(id, string(bs), ast.ParserOptions{RegoVersion: regoVersion}) + if err != nil { + return err + } + + loaded.Files.Modules[id] = &loader.RegoFile{ + Name: id, + Raw: bs, + Parsed: module, + } + } + } + } + + return f(ctx, txn, loaded) + }) +} + +func getWatchPaths(rootPaths []string) ([]string, error) { + paths := []string{} + + for _, path := range rootPaths { + _, path = loader.SplitPrefix(path) + + result, err := loader.Paths(path, true) + if err != nil { + return nil, err + } + + unique := map[string]struct{}{} + + for _, r := range result { + fi, err := os.Lstat(r) + if err != nil { + return nil, err + } + + if fi.IsDir() { + unique[r] = struct{}{} + } else { + dir := filepath.Dir(r) + unique[dir] = struct{}{} + } + } + + paths = append(paths, util.KeysSorted(unique)...) + } + + return paths, nil +} diff --git a/internal/pathwatcher/utils_test.go b/internal/pathwatcher/utils_test.go new file mode 100644 index 0000000..684c622 --- /dev/null +++ b/internal/pathwatcher/utils_test.go @@ -0,0 +1,107 @@ +// Copyright 2023 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 pathwatcher_test + +import ( + "context" + "os" + "path/filepath" + "slices" + "strings" + "testing" + + "github.com/aserto-dev/runtime/internal/file/archive" + "github.com/aserto-dev/runtime/internal/pathwatcher" + initload "github.com/aserto-dev/runtime/internal/runtime/init" + "github.com/aserto-dev/runtime/internal/storage/mock" + "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/storage" + "github.com/open-policy-agent/opa/v1/util/test" +) + +func TestWatchPaths(t *testing.T) { + fs := map[string]string{ + "/foo/bar/baz.json": "true", + "/foo/faz/baz.json": "true", + "/foo/baz.json": "true", + } + + expected := []string{ + "/foo", "/foo/bar", "/foo/faz", + } + + test.WithTempFS(fs, func(rootDir string) { + paths, err := pathwatcher.GetWatchPaths([]string{"prefix:" + rootDir + "/foo"}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + result := []string{} + for _, p := range paths { + result = append(result, filepath.Clean(strings.TrimPrefix(p, rootDir))) + } + + if !slices.Equal(expected, result) { + t.Fatalf("Expected %q but got: %q", expected, result) + } + }) +} + +func TestProcessWatcherUpdateForRegoVersion(t *testing.T) { + files := map[string]string{} + + test.WithTempFS(files, func(rootDir string) { + regoVersion := ast.RegoV1 + + // create a tar-ball bundle + tar := filepath.Join(rootDir, "bundle.tar.gz") + buf := archive.MustWriteTarGz(make([][2]string, 0, len(files))) + + bf, err := os.Create(tar) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + _, err = bf.Write(buf.Bytes()) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + // add only the bundle in the paths + paths := []string{ + tar, + } + filter := func(string, os.FileInfo, int) bool { + return false + } + f := func(ctx context.Context, txn storage.Transaction, loaded *initload.LoadPathsResult) error { + if loaded.Files.Modules == nil { + t.Fatalf("Unexpected nil loaded modules") + } + + return nil + } + + store := mock.New() + + // add a file that isn't registered in one of the paths + err = storage.Txn(t.Context(), store, storage.WriteParams, func(txn storage.Transaction) error { + err := store.UpsertPolicy(t.Context(), txn, "foo.rego", []byte(`package foo`)) + if err != nil { + t.Fatal(err) + } + + return nil + }) + if err != nil { + t.Fatal(err) + } + + err = pathwatcher.ProcessWatcherUpdateForRegoVersion(t.Context(), regoVersion, paths, "", store, filter, false, false, f) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + }) +} diff --git a/internal/runtime/init/init.go b/internal/runtime/init/init.go new file mode 100644 index 0000000..aa5e3c0 --- /dev/null +++ b/internal/runtime/init/init.go @@ -0,0 +1,263 @@ +// Copyright 2020 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 init is an internal package with helpers for data and policy loading during initialization. +package init + +import ( + "context" + "fmt" + "io/fs" + "path/filepath" + "strings" + + storedversion "github.com/aserto-dev/runtime/internal/version" + "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/bundle" + "github.com/open-policy-agent/opa/v1/loader" + "github.com/open-policy-agent/opa/v1/metrics" + "github.com/open-policy-agent/opa/v1/storage" + "github.com/open-policy-agent/opa/v1/util" +) + +// InsertAndCompileOptions contains the input for the operation. +type InsertAndCompileOptions struct { + Store storage.Store + Txn storage.Transaction + Files loader.Result + Bundles map[string]*bundle.Bundle + MaxErrors int + EnablePrintStatements bool + ParserOptions ast.ParserOptions + BundleActivatorPlugin string +} + +// InsertAndCompileResult contains the output of the operation. +type InsertAndCompileResult struct { + Compiler *ast.Compiler + Metrics metrics.Metrics +} + +// InsertAndCompile writes data and policy into the store and returns a compiler for the +// store contents. +func InsertAndCompile(ctx context.Context, opts InsertAndCompileOptions) (*InsertAndCompileResult, error) { + if len(opts.Files.Documents) > 0 { + if err := opts.Store.Write(ctx, opts.Txn, storage.AddOp, storage.RootPath, opts.Files.Documents); err != nil { + return nil, fmt.Errorf("storage error: %w", err) + } + } + + policies := make(map[string]*ast.Module, len(opts.Files.Modules)) + + for id, parsed := range opts.Files.Modules { + policies[id] = parsed.Parsed + } + + compiler := ast.NewCompiler(). + WithDefaultRegoVersion(opts.ParserOptions.RegoVersion). + SetErrorLimit(opts.MaxErrors). + WithPathConflictsCheck(storage.NonEmpty(ctx, opts.Store, opts.Txn)). + WithEnablePrintStatements(opts.EnablePrintStatements) + m := metrics.New() + + activation := &bundle.ActivateOpts{ + Ctx: ctx, + Store: opts.Store, + Txn: opts.Txn, + Compiler: compiler, + Metrics: m, + Bundles: opts.Bundles, + ExtraModules: policies, + ParserOptions: opts.ParserOptions, + Plugin: opts.BundleActivatorPlugin, + } + + err := bundle.Activate(activation) + if err != nil { + return nil, err + } + + // Policies in bundles will have already been added to the store, but + // modules loaded outside of bundles will need to be added manually. + for id, parsed := range opts.Files.Modules { + if err := opts.Store.UpsertPolicy(ctx, opts.Txn, id, parsed.Raw); err != nil { + return nil, fmt.Errorf("storage error: %w", err) + } + } + + // Set the version in the store last to prevent data files from overwriting. + if err := storedversion.Write(ctx, opts.Store, opts.Txn); err != nil { + return nil, fmt.Errorf("storage error: %w", err) + } + + return &InsertAndCompileResult{Compiler: compiler, Metrics: m}, nil +} + +// LoadPathsResult contains the output loading a set of paths. +type LoadPathsResult struct { + Bundles map[string]*bundle.Bundle + Files loader.Result +} + +// WalkPathsResult contains the output loading a set of paths. +type WalkPathsResult struct { + BundlesLoader []BundleLoader + FileDescriptors []*Descriptor +} + +// BundleLoader contains information about files in a bundle. +type BundleLoader struct { + DirectoryLoader bundle.DirectoryLoader + IsDir bool +} + +// Descriptor contains information about a file. +type Descriptor struct { + Root string + Path string +} + +// LoadPaths reads data and policy from the given paths and returns a set of bundles or +// raw loader file results. +func LoadPaths(paths []string, + filter loader.Filter, + asBundle bool, + bvc *bundle.VerificationConfig, + skipVerify bool, + bundleLazyLoading bool, + processAnnotations bool, + caps *ast.Capabilities, + fsys fs.FS, +) (*LoadPathsResult, error) { + return LoadPathsForRegoVersion(ast.RegoV0, paths, filter, asBundle, bvc, skipVerify, bundleLazyLoading, processAnnotations, false, caps, fsys) +} + +func LoadPathsForRegoVersion(regoVersion ast.RegoVersion, + paths []string, + filter loader.Filter, + asBundle bool, + bvc *bundle.VerificationConfig, + skipVerify bool, + bundleLazyLoading bool, + processAnnotations bool, + followSymlinks bool, + caps *ast.Capabilities, + fsys fs.FS, +) (*LoadPathsResult, error) { + if caps == nil { + caps = ast.CapabilitiesForThisVersion() + } + + // tar.gz files are automatically loaded as bundles + var likelyBundles, nonBundlePaths []string + if !asBundle { + likelyBundles, nonBundlePaths = splitByTarGzExt(paths) + paths = likelyBundles + } + + var ( + result LoadPathsResult + err error + ) + + if asBundle || len(likelyBundles) > 0 { + result.Bundles = make(map[string]*bundle.Bundle, len(paths)) + for _, path := range paths { + result.Bundles[path], err = loader.NewFileLoader(). + WithFS(fsys). + WithBundleVerificationConfig(bvc). + WithSkipBundleVerification(skipVerify). + WithBundleLazyLoadingMode(bundleLazyLoading). + WithFilter(filter). + WithProcessAnnotation(processAnnotations). + WithCapabilities(caps). + WithRegoVersion(regoVersion). + WithFollowSymlinks(followSymlinks). + AsBundle(path) + if err != nil { + return nil, err + } + } + } + + if asBundle { + return &result, nil + } + + files, err := loader.NewFileLoader(). + WithFS(fsys). + WithBundleLazyLoadingMode(bundleLazyLoading). + WithProcessAnnotation(processAnnotations). + WithCapabilities(caps). + WithRegoVersion(regoVersion). + Filtered(nonBundlePaths, filter) + if err != nil { + return nil, err + } + + result.Files = *files + + return &result, nil +} + +// splitByTarGzExt splits the paths in 2 groups. Ones with .tar.gz and another with +// non .tar.gz extensions. +func splitByTarGzExt(paths []string) (targzs []string, nonTargzs []string) { + for _, path := range paths { + if strings.HasSuffix(path, ".tar.gz") { + targzs = append(targzs, path) + } else { + nonTargzs = append(nonTargzs, path) + } + } + + return +} + +// WalkPaths reads data and policy from the given paths and returns a set of bundle directory loaders +// or descriptors that contain information about files. +func WalkPaths(paths []string, filter loader.Filter, asBundle bool) (*WalkPathsResult, error) { + var result WalkPathsResult + + if asBundle { + result.BundlesLoader = make([]BundleLoader, len(paths)) + for i, path := range paths { + bundleLoader, isDir, err := loader.GetBundleDirectoryLoader(path) + if err != nil { + return nil, err + } + + result.BundlesLoader[i] = BundleLoader{ + DirectoryLoader: bundleLoader, + IsDir: isDir, + } + } + + return &result, nil + } + + result.FileDescriptors = []*Descriptor{} + + for _, path := range paths { + filePaths, err := loader.FilteredPaths([]string{path}, filter) + if err != nil { + return nil, err + } + + for _, fp := range filePaths { + // Trim off the root directory and return path as if chrooted + cleanedPath := strings.TrimPrefix(fp, path) + if path == "." && filepath.Base(fp) == bundle.ManifestExt { + cleanedPath = fp + } + + result.FileDescriptors = append(result.FileDescriptors, &Descriptor{ + Root: path, + Path: util.WithPrefix(cleanedPath, "/"), + }) + } + } + + return &result, nil +} diff --git a/internal/runtime/init/init_test.go b/internal/runtime/init/init_test.go new file mode 100644 index 0000000..a6cbdb6 --- /dev/null +++ b/internal/runtime/init/init_test.go @@ -0,0 +1,532 @@ +// Copyright 2020 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 init_test + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "strings" + "testing" + + "github.com/aserto-dev/runtime/internal/file/archive" + initload "github.com/aserto-dev/runtime/internal/runtime/init" + "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/bundle" + "github.com/open-policy-agent/opa/v1/loader" + "github.com/open-policy-agent/opa/v1/storage" + inmem "github.com/open-policy-agent/opa/v1/storage/inmem/test" + "github.com/open-policy-agent/opa/v1/util" + "github.com/open-policy-agent/opa/v1/util/test" + "github.com/open-policy-agent/opa/v1/version" +) + +func TestInit(t *testing.T) { + mod1 := `package a.b.c + +import data.a.foo + +p = true { foo = "bar" } +p = true { 1 = 2 }` + + mod2 := `package b.c.d + +import data.b.foo + +p = true { foo = "bar" } +p = true { 1 = 2 }` + + tests := []struct { + note string + fs map[string]string + loadParams []string + expectedData map[string]string + expectedMods []string + asBundle bool + }{ + { + note: "load files", + fs: map[string]string{ + "datafile": `{"foo": "bar", "x": {"y": {"z": [1]}}}`, + "policyFile": mod1, + }, + loadParams: []string{"datafile", "policyFile"}, + expectedData: map[string]string{ + "/foo": "bar", + }, + expectedMods: []string{mod1}, + asBundle: false, + }, + { + note: "load bundle", + fs: map[string]string{ + "datafile": `{"foo": "bar", "x": {"y": {"z": [1]}}}`, // Should be ignored + "data.json": `{"foo": "not-bar"}`, + "policy.rego": mod1, + }, + loadParams: []string{"/"}, + expectedData: map[string]string{ + "/foo": "not-bar", + }, + expectedMods: []string{mod1}, + asBundle: true, + }, + { + note: "load multiple bundles", + fs: map[string]string{ + "/bundle1/a/data.json": `{"foo": "bar1", "x": {"y": {"z": [1]}}}`, // Should be ignored + "/bundle1/a/policy.rego": mod1, + "/bundle1/a/.manifest": `{"roots": ["a"]}`, + "/bundle2/b/data.json": `{"foo": "bar2"}`, + "/bundle2/b/policy.rego": mod2, + "/bundle2/b/.manifest": `{"roots": ["b"]}`, + }, + loadParams: []string{"bundle1", "bundle2"}, + expectedData: map[string]string{ + "/a/foo": "bar1", + "/b/foo": "bar2", + }, + expectedMods: []string{mod1, mod2}, + asBundle: true, + }, + { + note: "preserve OPA version", + fs: map[string]string{ + "/root/system/version/data.json": `{"version": "XYZ"}`, // Should be overwritten + }, + loadParams: []string{"root"}, + expectedData: map[string]string{ + "/system/version/version": version.Version, + }, + asBundle: true, + }, + } + + ctx := t.Context() + + for _, useMemoryFS := range []bool{false, true} { + for _, tc := range tests { + t.Run(tc.note, func(t *testing.T) { + test.WithTestFS(tc.fs, useMemoryFS, func(rootDir string, fsys fs.FS) { + paths := []string{} + + for _, fileName := range tc.loadParams { + paths = append(paths, filepath.Join(rootDir, fileName)) + } + // Create a new store and perform a file load/insert sequence. + store := inmem.New() + + err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error { + loaded, err := initload.LoadPaths(paths, nil, tc.asBundle, nil, false, true, false, nil, fsys) + if err != nil { + return err + } + + _, err = initload.InsertAndCompile(ctx, initload.InsertAndCompileOptions{ + Store: store, + Txn: txn, + Files: loaded.Files, + Bundles: loaded.Bundles, + MaxErrors: -1, + }) + + return err + }) + if err != nil { + t.Fatal(err) + } + + // Verify the loading was successful as expected. + txn := storage.NewTransactionOrDie(ctx, store) + + for storePath, expected := range tc.expectedData { + node, err := store.Read(ctx, txn, storage.MustParsePath(storePath)) + if util.Compare(node, expected) != 0 || err != nil { + t.Fatalf("Expected %v but got %v (err: %v)", expected, node, err) + } + } + + ids, err := store.ListPolicies(ctx, txn) + if err != nil { + t.Fatal(err) + } + + if len(tc.expectedMods) != len(ids) { + t.Fatalf("Expected %d modules, got %d", len(tc.expectedMods), len(ids)) + } + + actualMods := map[string]struct{}{} + + for _, id := range ids { + result, err := store.GetPolicy(ctx, txn, id) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + actualMods[string(result)] = struct{}{} + } + + for _, expectedMod := range tc.expectedMods { + if _, found := actualMods[expectedMod]; !found { + t.Fatalf("Expected %v but got: %v", expectedMod, actualMods) + } + } + + _, err = store.Read(ctx, txn, storage.MustParsePath("/system/version")) + if err != nil { + t.Fatal(err) + } + }) + }) + } + } +} + +func TestLoadTarGzsInBundleAndNonBundleMode(t *testing.T) { + type bundleInfo struct { + fileName string + files [][2]string + expBundle bundle.Bundle + } + + bundle1TarGz := bundleInfo{ + fileName: "bundle1.tar.gz", + files: [][2]string{ + {"/a/data.json", `{"foo": "bar1", "x": {"y": {"z": [1]}}}`}, + {"/a/.manifest", `{"roots": ["a"]}`}, + }, + expBundle: bundle.Bundle{ + Manifest: bundle.Manifest{ + Roots: &[]string{"a"}, + }, + Data: map[string]any{ + "a": map[string]any{ + "foo": "bar1", + "x": map[string]any{ + "y": map[string]any{ + "z": []any{json.Number("1")}, + }, + }, + }, + }, + }, + } + + bundle2TarGz := bundleInfo{ + fileName: "bundle2.tar.gz", + files: [][2]string{ + {"/b/data.json", `{"foo": "bar2", "x": {"y": {"z": [1]}}}`}, + {"/b/.manifest", `{"roots": ["b"]}`}, + }, + expBundle: bundle.Bundle{ + Manifest: bundle.Manifest{ + Roots: &[]string{"b"}, + }, + Data: map[string]any{ + "b": map[string]any{ + "foo": "bar2", + "x": map[string]any{ + "y": map[string]any{ + "z": []any{json.Number("1")}, + }, + }, + }, + }, + }, + } + + bundle1Folder := map[string]string{ + "/bundle1/a/data.json": `{"foo1": "bar2", "x": {"y": {"z": [2]}}}`, + "/bundle1/a/.manifest": `{"roots": ["a"]}`, + "/bundle1/a/foo.rego": `package a.b.y`, + } + + modulePath := "/bundle1/a/foo.rego" + module := `package a.b.y` + + bundle1FolderInfo := bundleInfo{ + fileName: "bundle1", + expBundle: bundle.Bundle{ + Manifest: bundle.Manifest{ + Roots: &[]string{"a"}, + }, + Data: map[string]any{ + "a": map[string]any{ + "foo1": "bar2", + "x": map[string]any{ + "y": map[string]any{ + "z": []any{json.Number("2")}, + }, + }, + }, + }, + Modules: []bundle.ModuleFile{ + { + URL: modulePath, + Path: modulePath, + Parsed: ast.MustParseModule(module), + Raw: []byte(module), + }, + }, + }, + } + + tests := []struct { + note string + bundleInfoTC []bundleInfo + folderContent map[string]string + expectedBundles int + expectedModules int + asBundle bool + }{ + { + note: "load multiple bundles. one tar.gz and one folder. Bundle mode is true", + folderContent: bundle1Folder, + bundleInfoTC: []bundleInfo{ + bundle1TarGz, + bundle1FolderInfo, + }, + expectedBundles: 2, + expectedModules: 0, + asBundle: true, + }, + { + note: "load multiple bundles. one tar.gz and one folder. Bundle mode is false", + folderContent: bundle1Folder, + bundleInfoTC: []bundleInfo{ + bundle1TarGz, + bundle1FolderInfo, + }, + expectedBundles: 1, + expectedModules: 1, + asBundle: false, + }, + { + note: "load multiple bundles. two tar.gz and one folder. Bundle mode is true", + folderContent: bundle1Folder, + bundleInfoTC: []bundleInfo{ + bundle1TarGz, + bundle2TarGz, + bundle1FolderInfo, + }, + expectedBundles: 3, + expectedModules: 0, + asBundle: true, + }, + { + note: "load multiple bundles. two tar.gz and one folder. Bundle mode is false", + folderContent: bundle1Folder, + bundleInfoTC: []bundleInfo{ + bundle1TarGz, + bundle2TarGz, + bundle1FolderInfo, + }, + expectedBundles: 2, + expectedModules: 1, + asBundle: false, + }, + { + note: "load just one folder. Bundle mode is true", + folderContent: bundle1Folder, + bundleInfoTC: []bundleInfo{ + bundle1FolderInfo, + }, + expectedBundles: 1, + expectedModules: 0, + asBundle: true, + }, + { + note: "load just one folder. Bundle mode is false", + folderContent: bundle1Folder, + bundleInfoTC: []bundleInfo{ + bundle1FolderInfo, + }, + expectedBundles: 0, + expectedModules: 1, + asBundle: false, + }, + } + + for _, tc := range tests { + t.Run(tc.note, func(t *testing.T) { + test.WithTempFS(tc.folderContent, func(rootDir string) { + paths := []string{} + + for _, bdlInfo := range tc.bundleInfoTC { + if strings.HasSuffix(bdlInfo.fileName, ".tar.gz") { + // Create the tar gz files temporarily + buf := archive.MustWriteTarGz(bdlInfo.files) + bundleFile := filepath.Join(rootDir, bdlInfo.fileName) + + out, err := os.Create(bundleFile) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + _, err = out.Write(buf.Bytes()) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + } + + paths = append(paths, filepath.Join(rootDir, bdlInfo.fileName)) + } + + loaded, err := initload.LoadPaths(paths, nil, tc.asBundle, nil, true, false, false, nil, nil) + if err != nil { + t.Fatal("Failed LoadPaths ", err) + } + + if tc.expectedBundles != len(loaded.Bundles) { + t.Fatalf("Expected %d bundles, got %d", tc.expectedBundles, len(loaded.Bundles)) + } + + if tc.expectedModules != len(loaded.Files.Modules) { + t.Fatalf("Expected %d modules, got %d", tc.expectedModules, len(loaded.Files.Modules)) + } + + // Testing the content + for path, actual := range loaded.Bundles { + for _, bdlInfo := range tc.bundleInfoTC { + if strings.HasSuffix(path, bdlInfo.fileName) { + var buf bytes.Buffer + + if err := bundle.NewWriter(&buf).Write(bdlInfo.expBundle); err != nil { + t.Fatal(err) + } + + expected, err := bundle.NewReader(&buf).Read() + if err != nil { + t.Fatal(err) + } + + // adjusting the URL and Path due to /tmp/ path + if len(bdlInfo.expBundle.Modules) > 0 { + expected.Modules[0].URL = rootDir + expected.Modules[0].URL + expected.Modules[0].Path = rootDir + expected.Modules[0].Path + } + + if !expected.Equal(*actual) { + t.Fatalf("\nExpected: %+v\nGot: %+v", expected, actual) + } + } + } + } + }) + }) + } +} + +func TestWalkPaths(t *testing.T) { + files := map[string]string{ + "/bundle1/a/data.json": `{"foo": "bar1", "x": {"y": {"z": [1]}}}`, + "/bundle1/a/policy.rego": `package example.foo`, + "/bundle1/a/.manifest": `{"roots": ["a"]}`, + "/bundle2/b/data.json": `{"foo": "bar2"}`, + "/bundle2/b/policy.rego": `package authz`, + "/bundle2/b/.manifest": `{"roots": ["b"]}`, + } + + test.WithTempFS(files, func(rootDir string) { + paths := []string{} + paths = append(paths, filepath.Join(rootDir, "bundle1"), filepath.Join(rootDir, "bundle2")) + + // bundle mode + loaded, err := initload.WalkPaths(paths, nil, true) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + if len(loaded.BundlesLoader) != len(paths) { + t.Fatalf("Expected %v bundle loaders but got %v", len(paths), len(loaded.BundlesLoader)) + } + + // check files + result := []string{} + + for _, bl := range loaded.BundlesLoader { + for { + f, err := bl.DirectoryLoader.NextFile() + + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + result = append(result, f.Path()) + + if _, ok := files[strings.TrimPrefix(f.URL(), rootDir)]; !ok { + t.Fatalf("unexpected file %v", f.Path()) + } + } + } + + if len(result) != len(files) { + t.Fatalf("Expected %v files across bundles but got %v", len(files), len(result)) + } + + // non-bundle mode + loaded, err = initload.WalkPaths(paths, nil, false) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + if len(loaded.FileDescriptors) != len(files) { + t.Fatalf("Expected %v files across directories but got %v", len(files), len(loaded.FileDescriptors)) + } + + for _, d := range loaded.FileDescriptors { + path := path.Join(d.Root, d.Path) + path = strings.TrimPrefix(path, rootDir) + + if _, ok := files[path]; !ok { + t.Fatalf("unexpected file %v", path) + } + } + }) +} + +func TestLoadPathsBundleModeWithFilter(t *testing.T) { + files := map[string]string{ + "a/data.json": `{"foo": "not-bar"}`, + "policy.rego": "package foo\n p = 1", + "policy_test.rego": "package foo\n test_p { p }", + "a/.manifest": `{"roots": ["a", "foo"]}`, + } + + test.WithTempFS(files, func(rootDir string) { + paths := []string{rootDir} + + // bundle mode + loaded, err := initload.LoadPaths(paths, func(abspath string, info os.FileInfo, depth int) bool { + return loader.GlobExcludeName("*_test.rego", 1)(abspath, info, depth) + }, true, nil, true, false, false, nil, nil) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + if len(loaded.Bundles) != len(paths) { + t.Fatalf("Expected %v bundle loaders but got %v", len(paths), len(loaded.Bundles)) + } + + b, ok := loaded.Bundles[rootDir] + if !ok { + t.Fatalf("expected bundle %v", rootDir) + } + + expected := 1 + if len(b.Modules) != expected { + t.Fatalf("expected %v module but got %v", expected, len(b.Modules)) + } + }) +} diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go new file mode 100644 index 0000000..0d47984 --- /dev/null +++ b/internal/runtime/runtime.go @@ -0,0 +1,60 @@ +// Copyright 2018 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 runtime contains utilities to return runtime information on the OPA instance. +package runtime + +import ( + "os" + "strings" + + "github.com/open-policy-agent/opa/v1/ast" + "github.com/open-policy-agent/opa/v1/util" + "github.com/open-policy-agent/opa/v1/version" +) + +// Params controls the types of runtime information to return. +type Params struct { + Config []byte + IsAuthorizationEnabled bool + SkipKnownSchemaCheck bool +} + +// Term returns the runtime information as an ast.Term object. +func Term(params Params) (*ast.Term, error) { + obj := ast.NewObject() + + if params.Config != nil { + var x any + if err := util.Unmarshal(params.Config, &x); err != nil { + return nil, err + } + + v, err := ast.InterfaceToValue(x) + if err != nil { + return nil, err + } + + obj.Insert(ast.InternedTerm("config"), ast.NewTerm(v)) + } + + env := ast.NewObject() + + for _, s := range os.Environ() { + parts := strings.SplitN(s, "=", 2) + if len(parts) == 1 { + env.Insert(ast.StringTerm(parts[0]), ast.InternedNullTerm) + } else if len(parts) > 1 { + env.Insert(ast.StringTerm(parts[0]), ast.StringTerm(parts[1])) + } + } + + obj.Insert(ast.InternedTerm("env"), ast.NewTerm(env)) + obj.Insert(ast.InternedTerm("version"), ast.StringTerm(version.Version)) + obj.Insert(ast.InternedTerm("commit"), ast.StringTerm(version.Vcs)) + obj.Insert(ast.InternedTerm("authorization_enabled"), ast.InternedTerm(params.IsAuthorizationEnabled)) + obj.Insert(ast.InternedTerm("skip_known_schema_check"), ast.InternedTerm(params.SkipKnownSchemaCheck)) + + return ast.NewTerm(obj), nil +} diff --git a/internal/storage/mock/mock.go b/internal/storage/mock/mock.go new file mode 100644 index 0000000..e6361b4 --- /dev/null +++ b/internal/storage/mock/mock.go @@ -0,0 +1,271 @@ +// 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 mock defines a fake storage implementation for use in testing. +package mock + +import ( + "context" + "fmt" + "testing" + + "github.com/open-policy-agent/opa/v1/storage" + "github.com/open-policy-agent/opa/v1/storage/inmem" +) + +// Transaction is a mock storage.Transaction implementation for use in testing. +// It uses an internal storage.Transaction pointer with some added functionality. +type Transaction struct { + txn storage.Transaction + Committed int + Aborted int +} + +// ID returns the underlying transaction ID. +func (t *Transaction) ID() uint64 { + return t.txn.ID() +} + +// Validate returns an error if the transaction is in an invalid state. +func (t *Transaction) Validate() error { + if t.Committed > 1 { + return fmt.Errorf("transaction %d has too many commits (%d)", t.ID(), t.Committed) + } + + if t.Aborted > 1 { + return fmt.Errorf("transaction %d has too many aborts (%d)", t.ID(), t.Committed) + } + + return nil +} + +func (t *Transaction) safeToUse() bool { + return t.Committed == 0 && t.Aborted == 0 +} + +// Store is a mock storage.Store implementation for use in testing. +type Store struct { + inmem storage.Store + storeOpts []inmem.Opt + baseData map[string]any + Transactions []*Transaction + Reads []*ReadCall + Writes []*WriteCall +} + +// ReadCall captures the parameters for a Read call. +type ReadCall struct { + Transaction *Transaction + Path storage.Path + Error error + Safe bool +} + +// WriteCall captures the parameters for a write call. +type WriteCall struct { + Transaction *Transaction + Op storage.PatchOp + Path storage.Path + Error error + Safe bool +} + +// New creates a new mock Store. +func New(opt ...inmem.Opt) *Store { + s := &Store{ + storeOpts: opt, + } + s.Reset() + + return s +} + +// NewWithData creates a store with some initial data. +func NewWithData(data map[string]any, opt ...inmem.Opt) *Store { + s := &Store{ + baseData: data, + storeOpts: opt, + } + s.Reset() + + return s +} + +// Reset the store. +func (s *Store) Reset() { + s.Transactions = []*Transaction{} + s.Reads = []*ReadCall{} + s.Writes = []*WriteCall{} + + if s.baseData != nil { + s.inmem = inmem.NewFromObjectWithOpts(s.baseData, s.storeOpts...) + } else { + s.inmem = inmem.NewWithOpts(s.storeOpts...) + } +} + +// GetTransaction will a transaction with a specific ID +// that was associated with this Store. +func (s *Store) GetTransaction(id uint64) *Transaction { + for _, txn := range s.Transactions { + if txn.ID() == id { + return txn + } + } + + return nil +} + +// Errors returns a list of errors for each invalid state found. +// If any Transactions are invalid or reads/writes were +// unsafe an error will be returned for each problem. +func (s *Store) Errors() []error { + var errs []error + + for _, txn := range s.Transactions { + err := txn.Validate() + if err != nil { + errs = append(errs, err) + } + } + + for _, read := range s.Reads { + if !read.Safe { + errs = append(errs, fmt.Errorf("unsafe Read call %+v", *read)) + } + } + + for _, write := range s.Writes { + if !write.Safe { + errs = append(errs, fmt.Errorf("unsafe Write call %+v", *write)) + } + } + + return errs +} + +// AssertValid will raise an error with the provided testing.T if +// there are any errors on the store. +func (s *Store) AssertValid(t *testing.T) { + t.Helper() + + for _, err := range s.Errors() { + t.Errorf("Error detected on store: %s", err) + } +} + +// storage.Store interface implementation + +// Register just shims the call to the underlying inmem store. +func (s *Store) Register(ctx context.Context, txn storage.Transaction, config storage.TriggerConfig) (storage.TriggerHandle, error) { + return s.inmem.Register(ctx, getRealTxn(txn), config) +} + +// ListPolicies just shims the call to the underlying inmem store. +func (s *Store) ListPolicies(ctx context.Context, txn storage.Transaction) ([]string, error) { + return s.inmem.ListPolicies(ctx, getRealTxn(txn)) +} + +// GetPolicy just shims the call to the underlying inmem store. +func (s *Store) GetPolicy(ctx context.Context, txn storage.Transaction, name string) ([]byte, error) { + return s.inmem.GetPolicy(ctx, getRealTxn(txn), name) +} + +// UpsertPolicy just shims the call to the underlying inmem store. +func (s *Store) UpsertPolicy(ctx context.Context, txn storage.Transaction, name string, policy []byte) error { + return s.inmem.UpsertPolicy(ctx, getRealTxn(txn), name, policy) +} + +// DeletePolicy just shims the call to the underlying inmem store. +func (s *Store) DeletePolicy(ctx context.Context, txn storage.Transaction, name string) error { + return s.inmem.DeletePolicy(ctx, getRealTxn(txn), name) +} + +// NewTransaction will create a new transaction on the underlying inmem store +// but wraps it with a mock Transaction. These are then tracked on the store. +func (s *Store) NewTransaction(ctx context.Context, params ...storage.TransactionParams) (storage.Transaction, error) { + realTxn, err := s.inmem.NewTransaction(ctx, params...) + if err != nil { + return nil, err + } + + txn := &Transaction{ + txn: realTxn, + Committed: 0, + Aborted: 0, + } + s.Transactions = append(s.Transactions, txn) + + return txn, nil +} + +// Read will make a read from the underlying inmem store and +// add a new entry to the mock store Reads list. If there +// is an error are the read is unsafe it will be noted in +// the ReadCall. +func (s *Store) Read(ctx context.Context, txn storage.Transaction, path storage.Path) (any, error) { + mockTxn := txn.(*Transaction) + + data, err := s.inmem.Read(ctx, mockTxn.txn, path) + + s.Reads = append(s.Reads, &ReadCall{ + Transaction: mockTxn, + Path: path, + Error: err, + Safe: mockTxn.safeToUse(), + }) + + return data, err +} + +// Write will make a read from the underlying inmem store and +// add a new entry to the mock store Writes list. If there +// is an error are the write is unsafe it will be noted in +// the WriteCall. +func (s *Store) Write(ctx context.Context, txn storage.Transaction, op storage.PatchOp, path storage.Path, value any) error { + mockTxn := txn.(*Transaction) + + err := s.inmem.Write(ctx, mockTxn.txn, op, path, value) + + s.Writes = append(s.Writes, &WriteCall{ + Transaction: mockTxn, + Op: op, + Path: path, + Error: err, + Safe: mockTxn.safeToUse(), + }) + + return nil +} + +// Commit will commit the underlying transaction while +// also updating the mock Transaction. +func (s *Store) Commit(ctx context.Context, txn storage.Transaction) error { + mockTxn := txn.(*Transaction) + + err := s.inmem.Commit(ctx, mockTxn.txn) + if err != nil { + return err + } + + mockTxn.Committed++ + + return nil +} + +// Abort will abort the underlying transaction while +// also updating the mock Transaction. +func (s *Store) Abort(ctx context.Context, txn storage.Transaction) { + mockTxn := txn.(*Transaction) + s.inmem.Abort(ctx, mockTxn.txn) + mockTxn.Aborted++ +} + +func (s *Store) Truncate(ctx context.Context, txn storage.Transaction, params storage.TransactionParams, it storage.Iterator) error { + return s.inmem.Truncate(ctx, getRealTxn(txn), params, it) +} + +func getRealTxn(txn storage.Transaction) storage.Transaction { + return txn.(*Transaction).txn +} diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..3f2e7a3 --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,35 @@ +// 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 version implements helper functions for the stored version. +package version + +import ( + "context" + "fmt" + "runtime" + + "github.com/open-policy-agent/opa/v1/storage" + "github.com/open-policy-agent/opa/v1/version" +) + +var versionPath = storage.MustParsePath("/system/version") + +// Write the build version information into storage. This makes the +// version information available to the REPL and the HTTP server. +func Write(ctx context.Context, store storage.Store, txn storage.Transaction) error { + if err := storage.MakeDir(ctx, store, txn, versionPath); err != nil { + return err + } + + return store.Write(ctx, txn, storage.AddOp, versionPath, map[string]any{ + "version": version.Version, + "build_commit": version.Vcs, + "build_timestamp": version.Timestamp, + "build_hostname": version.Hostname, + }) +} + +// UserAgent defines the current OPA instances User-Agent default header value. +var UserAgent = fmt.Sprintf("Open Policy Agent/%s (%s, %s)", version.Version, runtime.GOOS, runtime.GOARCH) diff --git a/local_bundle_watcher.go b/local_bundle_watcher.go index b892887..c665eb0 100644 --- a/local_bundle_watcher.go +++ b/local_bundle_watcher.go @@ -2,18 +2,12 @@ package runtime import ( "context" - "path/filepath" - "strings" "time" + "github.com/aserto-dev/runtime/internal/pathwatcher" + initload "github.com/aserto-dev/runtime/internal/runtime/init" "github.com/fsnotify/fsnotify" - "github.com/open-policy-agent/opa/v1/ast" - "github.com/open-policy-agent/opa/v1/bundle" - "github.com/open-policy-agent/opa/v1/loader" - "github.com/open-policy-agent/opa/v1/metrics" "github.com/open-policy-agent/opa/v1/storage" - "github.com/open-policy-agent/opa/v1/version" - "github.com/pkg/errors" ) func (r *Runtime) onReloadLogger(d time.Duration, err error) { @@ -34,228 +28,66 @@ func (r *Runtime) startWatcher(ctx context.Context, paths []string, onReload fun return nil } -func (r *Runtime) getWatcher(rootPaths []string) (*fsnotify.Watcher, error) { - watchPaths, err := getWatchPaths(rootPaths) - if err != nil { - return nil, err - } - - watcher, err := fsnotify.NewWatcher() - if err != nil { - return nil, err - } - - for _, path := range watchPaths { - r.Logger.Debug().Str("path", path).Msg("watching path") - - if err := watcher.Add(path); err != nil { - return nil, err - } - } - - if r.Config.LocalBundles.LocalPolicyImage != "" { - if err := watcher.Add(filepath.Join(r.Config.LocalBundles.FileStoreRoot, "policies-root", "index.json")); err != nil { - return nil, err - } - } - - return watcher, nil -} - -func getWatchPaths(rootPaths []string) ([]string, error) { - paths := []string{} - - for _, path := range rootPaths { - _, path = loader.SplitPrefix(path) - - result, err := loader.Paths(path, true) - if err != nil { - return nil, err - } - - paths = append(paths, loader.Dirs(result)...) - } - - return paths, nil -} - -func (r *Runtime) readWatcher(ctx context.Context, watcher *fsnotify.Watcher, paths []string, onReload func(time.Duration, error)) { +func (rt *Runtime) readWatcher(ctx context.Context, watcher *fsnotify.Watcher, paths []string, onReload func(time.Duration, error)) { for { - evt := <-watcher.Events - removalMask := (fsnotify.Remove | fsnotify.Rename) + select { + case evt := <-watcher.Events: + removalMask := fsnotify.Remove | fsnotify.Rename + mask := fsnotify.Create | fsnotify.Write | removalMask - mask := (fsnotify.Create | fsnotify.Write | removalMask) - if (evt.Op & mask) != 0 { - r.Logger.Debug().Str("event", evt.String()).Msg("registered file event") + if (evt.Op & mask) != 0 { + rt.Logger.Debug().Str("event", evt.String()).Msg("Registered file event") - t0 := time.Now() - removed := "" + t0 := time.Now() + removed := "" - if (evt.Op & removalMask) != 0 { - removed = evt.Name - } + if (evt.Op & removalMask) != 0 { + removed = evt.Name + } - err := r.processWatcherUpdate(ctx, paths, removed) - onReload(time.Since(t0), err) + err := rt.processWatcherUpdate(ctx, paths, removed) + onReload(time.Since(t0), err) + } + case <-ctx.Done(): + _ = watcher.Close() + return } } } -func (r *Runtime) processWatcherUpdate(ctx context.Context, paths []string, removed string) error { - if r.Config.LocalBundles.LocalPolicyImage != "" { - if err := r.deactivate(ctx); err != nil { - return err - } - } - - loadedBundles, err := r.loadPaths(paths) +func (rt *Runtime) getWatcher(rootPaths []string) (*fsnotify.Watcher, error) { + watcher, err := pathwatcher.CreatePathWatcher(rootPaths) if err != nil { - return err + return nil, err } - if removed != "" { - r.Logger.Debug().Msgf("Removed event name value: %v", removed) + for _, path := range watcher.WatchList() { + rt.Logger.Debug().Str("path", path).Msg("watching path") } - return storage.Txn(ctx, r.storage, storage.WriteParams, func(txn storage.Transaction) error { - _, err = insertAndCompile(ctx, &insertAndCompileOptions{ - Store: r.storage, - Txn: txn, - Bundles: loadedBundles, - MaxErrors: -1, - }) - if err != nil { - return err - } - - return nil - }) + return watcher, nil } -const ( - bundleRootOffset = 3 - rootindexOffset = 2 -) +func (rt *Runtime) processWatcherUpdate(ctx context.Context, paths []string, removed string) error { + return pathwatcher.ProcessWatcherUpdateForRegoVersion( + ctx, + rt.RegoVersion(), + paths, + removed, + rt.Store(), + rt.Params.Filter, + rt.Params.BundleMode, + rt.Params.BundleLazyLoadingMode, + func(ctx context.Context, txn storage.Transaction, loaded *initload.LoadPathsResult) error { + _, err := initload.InsertAndCompile(ctx, initload.InsertAndCompileOptions{ + Store: rt.Store(), + Txn: txn, + Files: loaded.Files, + Bundles: loaded.Bundles, + MaxErrors: -1, + ParserOptions: rt.GetPluginsManager().ParserOptions(), + }) -func (r *Runtime) deactivate(ctx context.Context) error { - err := storage.Txn(ctx, r.storage, storage.WriteParams, func(txn storage.Transaction) error { - deactivateMap := make(map[string]struct{}) - - policies, err := r.storage.ListPolicies(ctx, txn) - if err != nil { return err - } - - if len(policies) == 0 { - return nil - } - - path := strings.Split(policies[0], "/") - rootIndex := len(path) - bundleRootOffset // default bundle root. - - // bundle root detection for build images. - for i := range path { - if path[i] == "sha256" { - rootIndex = i + rootindexOffset - break - } - } - - root := strings.Join(path[:rootIndex], "/") - deactivateMap[root] = struct{}{} - - return bundle.Deactivate(&bundle.DeactivateOpts{ - Ctx: ctx, - Store: r.storage, - Txn: txn, - BundleNames: deactivateMap, }) - }) - - return err -} - -// insertAndCompileOptions contains input for the operation. -type insertAndCompileOptions struct { - Store storage.Store - Txn storage.Transaction - Files loader.Result - Bundles map[string]*bundle.Bundle - MaxErrors int -} - -// insertAndCompileResult contains the output of the operation. -type insertAndCompileResult struct { - Compiler *ast.Compiler - Metrics metrics.Metrics -} - -// insertAndCompile writes data and policy into the store and returns a compiler for the -// store contents. -func insertAndCompile(ctx context.Context, opts *insertAndCompileOptions) (*insertAndCompileResult, error) { - if len(opts.Files.Documents) > 0 { - if err := opts.Store.Write(ctx, opts.Txn, storage.AddOp, storage.Path{}, opts.Files.Documents); err != nil { - return nil, errors.Wrap(err, "storage error") - } - } - - policies := make(map[string]*ast.Module, len(opts.Files.Modules)) - - for id, parsed := range opts.Files.Modules { - policies[id] = parsed.Parsed - } - - compiler := ast.NewCompiler().SetErrorLimit(opts.MaxErrors).WithPathConflictsCheck(storage.NonEmpty(ctx, opts.Store, opts.Txn)) - m := metrics.New() - - activation := &bundle.ActivateOpts{ - Ctx: ctx, - Store: opts.Store, - Txn: opts.Txn, - Compiler: compiler, - Metrics: m, - Bundles: opts.Bundles, - ExtraModules: policies, - } - - err := bundle.Activate(activation) - if err != nil { - return nil, err - } - - // Policies in bundles will have already been added to the store, but - // modules loaded outside of bundles will need to be added manually. - for id, parsed := range opts.Files.Modules { - if err := opts.Store.UpsertPolicy(ctx, opts.Txn, id, parsed.Raw); err != nil { - return nil, errors.Wrap(err, "storage error") - } - } - - // Set the version in the store last to prevent data files from overwriting. - if err := writeVersion(ctx, opts.Store, opts.Txn); err != nil { - return nil, errors.Wrap(err, "storage error") - } - - return &insertAndCompileResult{Compiler: compiler, Metrics: m}, nil -} - -// writeVersion writes the build version information into storage. This makes the -// version information available to the REPL and the HTTP server. -func writeVersion(ctx context.Context, store storage.Store, txn storage.Transaction) error { - versionPath := storage.MustParsePath("/system/version") - - if err := storage.MakeDir(ctx, store, txn, versionPath); err != nil { - return err - } - - if err := store.Write(ctx, txn, storage.AddOp, versionPath, map[string]any{ - "version": version.Version, - "build_commit": version.Vcs, - "build_timestamp": version.Timestamp, - "build_hostname": version.Hostname, - }); err != nil { - return errors.Wrap(err, "failed to write version information to storage") - } - - return nil } diff --git a/runtime.go b/runtime.go index 6dd484c..9e926c5 100644 --- a/runtime.go +++ b/runtime.go @@ -18,6 +18,8 @@ import ( "github.com/open-policy-agent/opa/v1/loader" "github.com/open-policy-agent/opa/v1/metrics" "github.com/open-policy-agent/opa/v1/plugins" + rt "github.com/open-policy-agent/opa/v1/runtime" + bundleplugin "github.com/open-policy-agent/opa/v1/plugins/bundle" "github.com/open-policy-agent/opa/v1/plugins/discovery" opaStatus "github.com/open-policy-agent/opa/v1/plugins/status" @@ -58,6 +60,8 @@ type Runtime struct { storage storage.Store latestState atomic.Pointer[State] regoVersion ast.RegoVersion + + Params rt.Params } type BundleState struct { @@ -95,7 +99,7 @@ func New(ctx context.Context, cfg *Config, opts ...Option) (*Runtime, error) { pluginStates: &sync.Map{}, bundleStates: &sync.Map{}, plugins: map[string]plugins.Factory{}, - regoVersion: ast.RegoV1, + regoVersion: ast.RegoV0, } runtime.latestState.Store(&State{}) @@ -150,6 +154,14 @@ func (r *Runtime) Status() *State { return r.latestState.Load() } +func (r *Runtime) Store() storage.Store { + return r.storage +} + +func (r *Runtime) RegoVersion() ast.RegoVersion { + return r.regoVersion +} + // GetPluginsManager returns the runtime plugin manager. func (r *Runtime) GetPluginsManager() *plugins.Manager { return r.pluginsManager