mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: add prometheus metrics to database.Store (#7713)
* Adds dbmetrics package and wraps database.Store with a Prometheus HistogramVec of timings. * Adds Wrappers method to database.Store to avoid double-wrapping interfaces * Fixes test flake in TestLicensesListFake
This commit is contained in:
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/open-policy-agent/opa/topdown"
|
||||
@ -17,6 +18,8 @@ import (
|
||||
|
||||
var _ database.Store = (*querier)(nil)
|
||||
|
||||
const wrapname = "dbauthz.querier"
|
||||
|
||||
// NoActorError wraps ErrNoRows for the api to return a 404. This is the correct
|
||||
// response when the user is not authorized.
|
||||
var NoActorError = xerrors.Errorf("no authorization actor in context: %w", sql.ErrNoRows)
|
||||
@ -89,7 +92,7 @@ type querier struct {
|
||||
func New(db database.Store, authorizer rbac.Authorizer, logger slog.Logger) database.Store {
|
||||
// If the underlying db store is already a querier, return it.
|
||||
// Do not double wrap.
|
||||
if _, ok := db.(*querier); ok {
|
||||
if slices.Contains(db.Wrappers(), wrapname) {
|
||||
return db
|
||||
}
|
||||
return &querier{
|
||||
@ -99,6 +102,10 @@ func New(db database.Store, authorizer rbac.Authorizer, logger slog.Logger) data
|
||||
}
|
||||
}
|
||||
|
||||
func (q *querier) Wrappers() []string {
|
||||
return append(q.db.Wrappers(), wrapname)
|
||||
}
|
||||
|
||||
// authorizeContext is a helper function to authorize an action on an object.
|
||||
func (q *querier) authorizeContext(ctx context.Context, action rbac.Action, object rbac.Objecter) error {
|
||||
act, ok := ActorFromContext(ctx)
|
||||
|
@ -138,7 +138,7 @@ func TestDBAuthzRecursive(t *testing.T) {
|
||||
for i := 2; i < method.Type.NumIn(); i++ {
|
||||
ins = append(ins, reflect.New(method.Type.In(i)).Elem())
|
||||
}
|
||||
if method.Name == "InTx" || method.Name == "Ping" {
|
||||
if method.Name == "InTx" || method.Name == "Ping" || method.Name == "Wrappers" {
|
||||
continue
|
||||
}
|
||||
// Log the name of the last method, so if there is a panic, it is
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/uuid"
|
||||
"github.com/open-policy-agent/opa/topdown"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -19,14 +20,16 @@ import (
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/coderd/database/dbfake"
|
||||
"github.com/coder/coder/coderd/database/dbmock"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/rbac/regosql"
|
||||
"github.com/coder/coder/coderd/util/slice"
|
||||
)
|
||||
|
||||
var skipMethods = map[string]string{
|
||||
"InTx": "Not relevant",
|
||||
"Ping": "Not relevant",
|
||||
"InTx": "Not relevant",
|
||||
"Ping": "Not relevant",
|
||||
"Wrappers": "Not relevant",
|
||||
}
|
||||
|
||||
// TestMethodTestSuite runs MethodTestSuite.
|
||||
@ -52,7 +55,11 @@ type MethodTestSuite struct {
|
||||
// SetupSuite sets up the suite by creating a map of all methods on querier
|
||||
// and setting their count to 0.
|
||||
func (s *MethodTestSuite) SetupSuite() {
|
||||
az := dbauthz.New(nil, nil, slog.Make())
|
||||
ctrl := gomock.NewController(s.T())
|
||||
mockStore := dbmock.NewMockStore(ctrl)
|
||||
// We intentionally set no expectations apart from this.
|
||||
mockStore.EXPECT().Wrappers().Return([]string{}).AnyTimes()
|
||||
az := dbauthz.New(mockStore, nil, slog.Make())
|
||||
// Take the underlying type of the interface.
|
||||
azt := reflect.TypeOf(az).Elem()
|
||||
s.methodAccounting = make(map[string]int)
|
||||
|
Reference in New Issue
Block a user