// Copyright 2016 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 storage import ( "context" "github.com/open-policy-agent/opa/metrics" ) // Transaction defines the interface that identifies a consistent snapshot over // the policy engine's storage layer. type Transaction interface { ID() uint64 } // Store defines the interface for the storage layer's backend. type Store interface { Trigger Policy // NewTransaction is called create a new transaction in the store. NewTransaction(context.Context, ...TransactionParams) (Transaction, error) // Read is called to fetch a document referred to by path. Read(context.Context, Transaction, Path) (interface{}, error) // Write is called to modify a document referred to by path. Write(context.Context, Transaction, PatchOp, Path, interface{}) error // Commit is called to finish the transaction. If Commit returns an error, the // transaction must be automatically aborted by the Store implementation. Commit(context.Context, Transaction) error // Truncate is called to make a copy of the underlying store, write documents in the new store // by creating multiple transactions in the new store as needed and finally swapping // over to the new storage instance. This method must be called within a transaction on the original store. Truncate(context.Context, Transaction, TransactionParams, Iterator) error // Abort is called to cancel the transaction. Abort(context.Context, Transaction) } // MakeDirer defines the interface a Store could realize to override the // generic MakeDir functionality in storage.MakeDir type MakeDirer interface { MakeDir(context.Context, Transaction, Path) error } // TransactionParams describes a new transaction. type TransactionParams struct { // BasePaths indicates the top-level paths where write operations will be performed in this transaction. BasePaths []string // RootOverwrite is deprecated. Use BasePaths instead. RootOverwrite bool // Write indicates if this transaction will perform any write operations. Write bool // Context contains key/value pairs passed to triggers. Context *Context } // Context is a simple container for key/value pairs. type Context struct { values map[interface{}]interface{} } // NewContext returns a new context object. func NewContext() *Context { return &Context{ values: map[interface{}]interface{}{}, } } // Get returns the key value in the context. func (ctx *Context) Get(key interface{}) interface{} { if ctx == nil { return nil } return ctx.values[key] } // Put adds a key/value pair to the context. func (ctx *Context) Put(key, value interface{}) { ctx.values[key] = value } var metricsKey = struct{}{} // WithMetrics allows passing metrics via the Context. // It puts the metrics object in the ctx, and returns the same // ctx (not a copy) for convenience. func (ctx *Context) WithMetrics(m metrics.Metrics) *Context { ctx.values[metricsKey] = m return ctx } // Metrics() allows using a Context's metrics. Returns nil if metrics // were not attached to the Context. func (ctx *Context) Metrics() metrics.Metrics { if m, ok := ctx.values[metricsKey]; ok { if met, ok := m.(metrics.Metrics); ok { return met } } return nil } // WriteParams specifies the TransactionParams for a write transaction. var WriteParams = TransactionParams{ Write: true, } // PatchOp is the enumeration of supposed modifications. type PatchOp int // Patch supports add, remove, and replace operations. const ( AddOp PatchOp = iota RemoveOp = iota ReplaceOp = iota ) // WritesNotSupported provides a default implementation of the write // interface which may be used if the backend does not support writes. type WritesNotSupported struct{} func (WritesNotSupported) Write(context.Context, Transaction, PatchOp, Path, interface{}) error { return writesNotSupportedError() } // Policy defines the interface for policy module storage. type Policy interface { ListPolicies(context.Context, Transaction) ([]string, error) GetPolicy(context.Context, Transaction, string) ([]byte, error) UpsertPolicy(context.Context, Transaction, string, []byte) error DeletePolicy(context.Context, Transaction, string) error } // PolicyNotSupported provides a default implementation of the policy interface // which may be used if the backend does not support policy storage. type PolicyNotSupported struct{} // ListPolicies always returns a PolicyNotSupportedErr. func (PolicyNotSupported) ListPolicies(context.Context, Transaction) ([]string, error) { return nil, policyNotSupportedError() } // GetPolicy always returns a PolicyNotSupportedErr. func (PolicyNotSupported) GetPolicy(context.Context, Transaction, string) ([]byte, error) { return nil, policyNotSupportedError() } // UpsertPolicy always returns a PolicyNotSupportedErr. func (PolicyNotSupported) UpsertPolicy(context.Context, Transaction, string, []byte) error { return policyNotSupportedError() } // DeletePolicy always returns a PolicyNotSupportedErr. func (PolicyNotSupported) DeletePolicy(context.Context, Transaction, string) error { return policyNotSupportedError() } // PolicyEvent describes a change to a policy. type PolicyEvent struct { ID string Data []byte Removed bool } // DataEvent describes a change to a base data document. type DataEvent struct { Path Path Data interface{} Removed bool } // TriggerEvent describes the changes that caused the trigger to be invoked. type TriggerEvent struct { Policy []PolicyEvent Data []DataEvent Context *Context } // IsZero returns true if the TriggerEvent indicates no changes occurred. This // function is primarily for test purposes. func (e TriggerEvent) IsZero() bool { return !e.PolicyChanged() && !e.DataChanged() } // PolicyChanged returns true if the trigger was caused by a policy change. func (e TriggerEvent) PolicyChanged() bool { return len(e.Policy) > 0 } // DataChanged returns true if the trigger was caused by a data change. func (e TriggerEvent) DataChanged() bool { return len(e.Data) > 0 } // TriggerConfig contains the trigger registration configuration. type TriggerConfig struct { // OnCommit is invoked when a transaction is successfully committed. The // callback is invoked with a handle to the write transaction that // successfully committed before other clients see the changes. OnCommit func(context.Context, Transaction, TriggerEvent) } // Trigger defines the interface that stores implement to register for change // notifications when the store is changed. type Trigger interface { Register(context.Context, Transaction, TriggerConfig) (TriggerHandle, error) } // TriggersNotSupported provides default implementations of the Trigger // interface which may be used if the backend does not support triggers. type TriggersNotSupported struct{} // Register always returns an error indicating triggers are not supported. func (TriggersNotSupported) Register(context.Context, Transaction, TriggerConfig) (TriggerHandle, error) { return nil, triggersNotSupportedError() } // TriggerHandle defines the interface that can be used to unregister triggers that have // been registered on a Store. type TriggerHandle interface { Unregister(context.Context, Transaction) } // Iterator defines the interface that can be used to read files from a directory starting with // files at the base of the directory, then sub-directories etc. type Iterator interface { Next() (*Update, error) } // Update contains information about a file type Update struct { Path Path Value []byte IsPolicy bool }