import "github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/authorizer"
- Constants
- type Authorizer
- type ContextKey
- type ContextLessAuthorizer
- func (s *ContextLessAuthorizer) Configure(tableName string, roleMapping map[string]dbrole.DbRole)
- func (s *ContextLessAuthorizer) GetAuthContext(orgId string, roles ...string) context.Context
- func (s *ContextLessAuthorizer) GetDefaultOrgAdminContext() context.Context
- func (s *ContextLessAuthorizer) GetMatchingDbRole(_ context.Context, tableNames ...string) (dbrole.DbRole, error)
- func (s *ContextLessAuthorizer) GetOrgFromContext(_ context.Context) (string, error)
- type Instancer
- type MetadataBasedAuthorizer
- func (s *MetadataBasedAuthorizer) Configure(tableName string, roleMapping map[string]dbrole.DbRole)
- func (s *MetadataBasedAuthorizer) GetAuthContext(orgId string, roles ...string) context.Context
- func (s *MetadataBasedAuthorizer) GetDefaultOrgAdminContext() context.Context
- func (s *MetadataBasedAuthorizer) GetMatchingDbRole(ctx context.Context, tableNames ...string) (dbrole.DbRole, error)
- func (s *MetadataBasedAuthorizer) GetOrgFromContext(ctx context.Context) (string, error)
- type SimpleInstancer
- type SimpleTransactionFetcher
- type Tenancer
- type TransactionContextKey
- type TransactionFetcher
const (
GLOBAL_DEFAULT_ORG_ID = "_GlobalDefaultOrg"
METADATA_KEY_ORGID = "orgid"
METADATA_KEY_ROLE = "role"
METADATA_ROLE_SERVICE_ADMIN = "service_admin"
METADATA_ROLE_SERVICE_AUDITOR = "service_auditor"
METADATA_ROLE_ADMIN = "admin" // can be tenant_admin, *_admin
METADATA_ROLE_AUDITOR = "auditor" // can be tenant_auditor, *_auditor
)
const (
INSTANCE_ID = ContextKey("multiinstance.id")
)
const (
TransactionCtx = TransactionContextKey("DB_TRANSACTION")
)
type Authorizer
Authorizer Interface defines the methods required for datastore to restrict access based on roles configured in context.
type Authorizer interface {
Tenancer
Configure(tableName string, roleMapping map[string]dbrole.DbRole)
GetAuthContext(orgId string, roles ...string) context.Context
GetDefaultOrgAdminContext() context.Context
GetMatchingDbRole(ctx context.Context, tableNames ...string) (dbrole.DbRole, error)
}
type ContextKey
type ContextKey string
type ContextLessAuthorizer struct {
// contains filtered or unexported fields
}
func (*ContextLessAuthorizer) Configure
func (s *ContextLessAuthorizer) Configure(tableName string, roleMapping map[string]dbrole.DbRole)
func (*ContextLessAuthorizer) GetAuthContext
func (s *ContextLessAuthorizer) GetAuthContext(orgId string, roles ...string) context.Context
func (*ContextLessAuthorizer) GetDefaultOrgAdminContext
func (s *ContextLessAuthorizer) GetDefaultOrgAdminContext() context.Context
func (*ContextLessAuthorizer) GetMatchingDbRole
func (s *ContextLessAuthorizer) GetMatchingDbRole(_ context.Context, tableNames ...string) (dbrole.DbRole, error)
func (*ContextLessAuthorizer) GetOrgFromContext
func (s *ContextLessAuthorizer) GetOrgFromContext(_ context.Context) (string, error)
type Instancer
type Instancer interface {
GetInstanceId(ctx context.Context) (string, error)
WithInstanceId(ctx context.Context, instanceId string) context.Context
}
type MetadataBasedAuthorizer struct {
// contains filtered or unexported fields
}
func (*MetadataBasedAuthorizer) Configure
func (s *MetadataBasedAuthorizer) Configure(tableName string, roleMapping map[string]dbrole.DbRole)
func (*MetadataBasedAuthorizer) GetAuthContext
func (s *MetadataBasedAuthorizer) GetAuthContext(orgId string, roles ...string) context.Context
func (*MetadataBasedAuthorizer) GetDefaultOrgAdminContext
func (s *MetadataBasedAuthorizer) GetDefaultOrgAdminContext() context.Context
func (*MetadataBasedAuthorizer) GetMatchingDbRole
func (s *MetadataBasedAuthorizer) GetMatchingDbRole(ctx context.Context, tableNames ...string) (dbrole.DbRole, error)
func (*MetadataBasedAuthorizer) GetOrgFromContext
func (s *MetadataBasedAuthorizer) GetOrgFromContext(ctx context.Context) (string, error)
type SimpleInstancer
type SimpleInstancer struct{}
func (*SimpleInstancer) GetInstanceId
func (s *SimpleInstancer) GetInstanceId(ctx context.Context) (string, error)
func (*SimpleInstancer) WithInstanceId
func (s *SimpleInstancer) WithInstanceId(ctx context.Context, instanceId string) context.Context
type SimpleTransactionFetcher struct{}
func (SimpleTransactionFetcher) GetTransactionCtx
func (s SimpleTransactionFetcher) GetTransactionCtx(ctx context.Context) *gorm.DB
func (SimpleTransactionFetcher) IsTransactionCtx
func (s SimpleTransactionFetcher) IsTransactionCtx(ctx context.Context) bool
func (SimpleTransactionFetcher) WithTransactionCtx
func (s SimpleTransactionFetcher) WithTransactionCtx(ctx context.Context, tx *gorm.DB) context.Context
type Tenancer
type Tenancer interface {
GetOrgFromContext(ctx context.Context) (string, error)
}
type TransactionContextKey string
type TransactionFetcher
type TransactionFetcher interface {
IsTransactionCtx(ctx context.Context) bool
GetTransactionCtx(ctx context.Context) *gorm.DB
WithTransactionCtx(ctx context.Context, tx *gorm.DB) context.Context
}
import "github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/datastore"
Import the package and use DataStore interface to interact with the data access layer. If you want DAL to use a Postgres database, ensure you have the following environment variables set to relevant values: [DB_ADMIN_USERNAME], [DB_PORT], [DB_NAME], [DB_ADMIN_PASSWORD], [DB_HOST], [SSL_MODE]. You can also set [LOG_LEVEL] environment variable to debug/trace, if you want logging at a specific level (default is [Info])
Define structs that will be persisted using datastore similar to any gorm Models, for reference https://gorm.io/docs/models.html
- At least one field must be a primary key with `gorm:"primaryKey"` tag
- For multi-tenancy support, add `gorm:"column:org_id"` as tag to a filed
- For revision support to block concurrent updates, add `gorm:"column:revision"` as tag
- For multi-instance support, add `gorm:"column:instance_id"` as tag
DataStore interface exposes basic methods like Find/FindAll/Upsert/Delete. For richer queries and performing a set of operations within a transaction, please, use GetTransaction() method. For more info, refer to Gorm's transactions page: https://gorm.io/docs/transactions.html
- Constants
- func DBCreate(cfg DBConfig) error
- func DBExists(cfg DBConfig) bool
- func GetFieldValue(record Record, fieldName, columnName string) (string, bool)
- func GetInstanceId(record Record) (string, bool)
- func GetOrgId(record Record) (string, bool)
- func GetTableName(x interface{}) (tableName string)
- func IsColumnPresent(x Record, tableName, columnName string) bool
- func IsMultiInstanced(x Record, tableName string, instancerConfigured bool) bool
- func IsMultiTenanted(x Record, tableName string) bool
- func IsPointerToStruct(x interface{}) (isPtrType bool)
- func IsRevisioned(x Record, tableName string) bool
- func IsRowLevelSecurityRequired(record Record, tableName string, instancerConfigured bool) bool
- func SetFieldValue(record Record, fieldName, columnName, value string) bool
- func SetInstanceId(record Record, value string) bool
- func TypeName(x interface{}) string
- type DBConfig
- type DataStore
- func FromConfig(l *logrus.Entry, a authorizer.Authorizer, instancer authorizer.Instancer, cfg DBConfig) (d DataStore, err error)
- func FromEnv(l *logrus.Entry, a authorizer.Authorizer, instancer authorizer.Instancer) (d DataStore, err error)
- func FromEnvWithDB(l *logrus.Entry, a authorizer.Authorizer, instancer authorizer.Instancer, dbName string) (d DataStore, err error)
- type Helper
- type Pagination
- type Record
- type TenancyInfo
- type TestHelper
const (
DbConfigOrgId = "multitenant.orgId" // Name of Postgres run-time config. parameter that will store current user's org. ID
DbConfigInstanceId = "multitenant.instanceId" // Name of Postgres run-time config. parameter that will store current session's instance ID
MaxIdleConns = 1
)
const (
// Env. variable names.
DB_NAME_ENV_VAR = "DB_NAME"
DB_PORT_ENV_VAR = "DB_PORT"
DB_HOST_ENV_VAR = "DB_HOST"
SSL_MODE_ENV_VAR = "SSL_MODE"
DB_ADMIN_USERNAME_ENV_VAR = "DB_ADMIN_USERNAME"
DB_ADMIN_PASSWORD_ENV_VAR = "DB_ADMIN_PASSWORD"
// SQL Error Codes.
ERROR_DUPLICATE_KEY = "SQLSTATE 23505"
ERROR_DUPLICATE_DATABASE = "SQLSTATE 42P04"
)
const (
DEFAULT_OFFSET = 0
DEFAULT_LIMIT = 1000
DEFAULT_SORTBY = ""
)
const (
// Struct Field Names.
FIELD_ORGID = "OrgId"
FIELD_INSTANCEID = "InstanceId"
// SQL Columns.
COLUMN_ORGID = "org_id"
COLUMN_INSTANCEID = "instance_id"
COLUMN_REVISION = "revision"
// Messages.
REVISION_OUTDATED_MSG = "Invalid update - outdated "
)
func DBCreate
func DBCreate(cfg DBConfig) error
Create a Postgres DB using the provided config if it doesn't exist.
func DBExists
func DBExists(cfg DBConfig) bool
Checks if a Postgres DB exists and returns true.
func GetFieldValue
func GetFieldValue(record Record, fieldName, columnName string) (string, bool)
Returns the requested fields value from record, which is a pointer to a struct implementing Record interface. Uses a tag rather than field name to find the desired field. Returns an empty string and false if such a field is not present.
func GetInstanceId
func GetInstanceId(record Record) (string, bool)
Returns the requested InstanceId field's value from record, which is a pointer to a struct implementing Record interface. Uses a tag rather than field name to find the desired field. Returns an empty string and false if such a field is not present.
func GetOrgId
func GetOrgId(record Record) (string, bool)
Returns the requested OrgId field's value from record, which is a pointer to a struct implementing Record interface. Uses a tag rather than field name to find the desired field. Returns an empty string and false if such a field is not present.
func GetTableName
func GetTableName(x interface{}) (tableName string)
Extracts struct's name, which will serve as DB table name, using reflection.
func IsColumnPresent
func IsColumnPresent(x Record, tableName, columnName string) bool
func IsMultiInstanced
func IsMultiInstanced(x Record, tableName string, instancerConfigured bool) bool
Checks if multiple deployment instances are supported in the given table.
func IsMultiTenanted
func IsMultiTenanted(x Record, tableName string) bool
Checks if multiple tenants are supported in the given table.
func IsPointerToStruct
func IsPointerToStruct(x interface{}) (isPtrType bool)
func IsRevisioned
func IsRevisioned(x Record, tableName string) bool
Checks if revisioning is supported in the given table.
func IsRowLevelSecurityRequired(record Record, tableName string, instancerConfigured bool) bool
Row Level Security to used to partition tables for multi-tenancy and multi-instance support.
func SetFieldValue
func SetFieldValue(record Record, fieldName, columnName, value string) bool
func SetInstanceId
func SetInstanceId(record Record, value string) bool
func TypeName
func TypeName(x interface{}) string
TypeName returns name of the data type of the given variable.
type DBConfig
type DBConfig struct {
// contains filtered or unexported fields
}
func ConfigFromEnv
func ConfigFromEnv(dbName string) DBConfig
Returns DBConfig constructed from the env variables. If dbName is set, it is used instead of DB_NAME env variable. All env variables are required and if not set, this method would panic.
type DataStore
type DataStore interface {
Find(ctx context.Context, record Record) error
FindSoftDeleted(ctx context.Context, record Record) error
FindAll(ctx context.Context, records interface{}, pagination *Pagination) error
FindAllIncludingSoftDeleted(ctx context.Context, records interface{}, pagination *Pagination) error
FindWithFilter(ctx context.Context, filter Record, records interface{}, pagination *Pagination) error
FindWithFilterIncludingSoftDeleted(ctx context.Context, filter Record, records interface{}, pagination *Pagination) error
Insert(ctx context.Context, record Record) (int64, error)
SoftDelete(ctx context.Context, record Record) (int64, error)
Delete(ctx context.Context, record Record) (int64, error)
Update(ctx context.Context, record Record) (int64, error)
Upsert(ctx context.Context, record Record) (int64, error)
GetTransaction(ctx context.Context, record ...Record) (tx *gorm.DB, err error)
// Create a DB table for the given struct. Enables RLS in it if it is multi-tenant.
// Generates Postgres roles and policies based on the provided role mapping and applies them
// to the created DB table.
// roleMapping - maps service roles to DB roles to be used for the generated DB table
// There are 4 possible DB roles to choose from:
// - READER, which gives read access to all the records in the table
// - WRITER, which gives read & write access to all the records in the table
// - TENANT_READER, which gives read access to current tenant's records
// - TENANT_WRITER, which gives read & write access to current tenant's records.
Register(ctx context.Context, roleMapping map[string]dbrole.DbRole, records ...Record) error
Reset()
GetAuthorizer() authorizer.Authorizer
GetInstancer() authorizer.Instancer
Helper() Helper
TestHelper() TestHelper
}
Example (Multi Instance)
Example for the multi-instance feature, illustrates one can create records for different instances and that each instance context can access the data that belongs to specific instance only.
package main
import (
"context"
"fmt"
"log"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/authorizer"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/datastore"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/dbrole"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/logutils"
)
type Person struct {
Id string `gorm:"primaryKey"`
Name string
Age int
InstanceId string `gorm:"primaryKey"`
}
func (p Person) String() string {
return fmt.Sprintf("[%s/%s] %s: %d", p.InstanceId, p.Id, p.Name, p.Age)
}
// Example for the multi-instance feature, illustrates one can create records for different instances
// and that each instance context can access the data that belongs to specific instance only.
func main() {
uId := "P1337"
p1 := &Person{uId, "Bob", 31, "Dev"}
p2 := &Person{uId, "John", 36, "Prod"}
p3 := &Person{"P3", "Pat", 39, "Dev"}
SERVICE_ADMIN := "service_admin"
SERVICE_AUDITOR := "service_auditor"
mdAuthorizer := &authorizer.MetadataBasedAuthorizer{}
instancer := &authorizer.SimpleInstancer{}
ServiceAdminCtx := mdAuthorizer.GetAuthContext("", SERVICE_ADMIN)
DevInstanceCtx := instancer.WithInstanceId(ServiceAdminCtx, "Dev")
ProdInstanceCtx := instancer.WithInstanceId(ServiceAdminCtx, "Prod")
// Initializes the Datastore using the metadata authorizer and connection details obtained from the ENV variables.
ds, err := datastore.FromEnvWithDB(logutils.GetCompLogger(), mdAuthorizer, instancer, "ExampleDataStore_multiInstance")
defer ds.Reset()
if err != nil {
log.Fatalf("datastore initialization from env errored: %s", err)
}
// Registers the necessary structs with their corresponding role mappings.
roleMapping := map[string]dbrole.DbRole{
SERVICE_AUDITOR: dbrole.INSTANCE_READER,
SERVICE_ADMIN: dbrole.INSTANCE_WRITER,
}
if err = ds.Register(context.TODO(), roleMapping, &Person{}); err != nil {
log.Fatalf("Failed to create DB tables: %+v", err)
}
// Inserts a record with a given Id (uId) using the context of the Dev instance.
rowsAffected, err := ds.Insert(DevInstanceCtx, p1)
fmt.Println(rowsAffected, err)
// Inserts another record with the same uId using the context of the Prod instance.
rowsAffected, err = ds.Insert(ProdInstanceCtx, p2)
fmt.Println(rowsAffected, err)
// Inserts a third record with a different uId using the context of the Dev instance.
rowsAffected, err = ds.Insert(DevInstanceCtx, p3)
fmt.Println(rowsAffected, err)
// Finds a record using the context of the Dev instance and the specified uId.
q1 := &Person{Id: uId}
err = ds.Find(DevInstanceCtx, q1)
fmt.Println(q1, err)
// Finds a record using the context of the Prod instance and the same uId.
q2 := &Person{Id: uId}
err = ds.Find(ProdInstanceCtx, q2)
fmt.Println(q2, err)
// Finds a record using the correct context of the Dev instance.
q3 := &Person{Id: "P3"}
err = ds.Find(DevInstanceCtx, q3)
fmt.Println(q3, err)
// Attempts to find a record using the incorrect context of the Prod instance and should error out.
q4 := &Person{Id: "P3"}
err = ds.Find(ProdInstanceCtx, q4)
fmt.Printf("err != nil - %t\n", err != nil)
// Deletes a record using the context of the Dev instance and the specified uId.
rowsAffected, err = ds.Delete(DevInstanceCtx, q1)
fmt.Println(rowsAffected, err)
// Deletes a record using the context of the Prod instance and the same uId.
rowsAffected, err = ds.Delete(ProdInstanceCtx, q2)
fmt.Println(rowsAffected, err)
// Attempts to delete a record using an invalid context of the Prod instance, which should not affect the database.
rowsAffected, err = ds.Delete(ProdInstanceCtx, q4)
fmt.Println(rowsAffected, err)
// Deletes a record using a valid context of the Dev instance.
rowsAffected, err = ds.Delete(DevInstanceCtx, q3)
fmt.Println(rowsAffected, err)
}
1 <nil>
1 <nil>
1 <nil>
[Dev/P1337] Bob: 31 <nil>
[Prod/P1337] John: 36 <nil>
[Dev/P3] Pat: 39 <nil>
err != nil - true
1 <nil>
1 <nil>
0 <nil>
1 <nil>
Example (Multi Tenancy)
Example for the multi-tenancy feature, illustrates one can create records for different tenants and that each tenant context can access the data that belongs to specific tenant only.
package main
import (
"context"
"fmt"
"log"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/authorizer"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/datastore"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/dbrole"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/logutils"
)
type User struct {
Id string `gorm:"primaryKey"`
Name string
Age int
OrgId string `gorm:"primaryKey"`
}
func (p User) String() string {
return fmt.Sprintf("[%s/%s] %s: %d", p.OrgId, p.Id, p.Name, p.Age)
}
// Example for the multi-tenancy feature, illustrates one can create records for different tenants
// and that each tenant context can access the data that belongs to specific tenant only.
func main() {
uId := "P1337"
p1 := &User{uId, "Bob", 31, "Coke"}
p2 := &User{uId, "John", 36, "Pepsi"}
p3 := &User{"P3", "Pat", 39, "Coke"}
TENANT_ADMIN := "tenant_admin"
TENANT_AUDITOR := "tenant_auditor"
mdAuthorizer := &authorizer.MetadataBasedAuthorizer{}
CokeOrgCtx := mdAuthorizer.GetAuthContext("Coke", TENANT_ADMIN)
PepsiOrgCtx := mdAuthorizer.GetAuthContext("Pepsi", TENANT_ADMIN)
// Initializes the Datastore using the metadata authorizer and connection details obtained from the ENV variables.
ds, err := datastore.FromEnvWithDB(logutils.GetCompLogger(), mdAuthorizer, nil, "ExampleDataStore_multiTenancy")
defer ds.Reset()
if err != nil {
log.Fatalf("datastore initialization from env errored: %s", err)
}
// Registers the necessary structs with their corresponding tenant role mappings.
roleMapping := map[string]dbrole.DbRole{
TENANT_AUDITOR: dbrole.TENANT_READER,
TENANT_ADMIN: dbrole.TENANT_WRITER,
}
if err = ds.Register(context.TODO(), roleMapping, &User{}); err != nil {
log.Fatalf("Failed to create DB tables: %+v", err)
}
// Inserts a record with a given Id (uId) using the context of the Coke organization.
rowsAffected, err := ds.Insert(CokeOrgCtx, p1)
fmt.Println(rowsAffected, err)
// Inserts another record with the same uId using the context of the Pepsi organization.
rowsAffected, err = ds.Insert(PepsiOrgCtx, p2)
fmt.Println(rowsAffected, err)
// Inserts a third record with a different uId using the context of the Coke organization.
rowsAffected, err = ds.Insert(CokeOrgCtx, p3)
fmt.Println(rowsAffected, err)
// Finds a record using the context of the Coke organization and the specified uId.
q1 := &User{Id: uId}
err = ds.Find(CokeOrgCtx, q1)
fmt.Println(q1, err)
// Finds a record using the context of the Pepsi organization and the same uId.
q2 := &User{Id: uId}
err = ds.Find(PepsiOrgCtx, q2)
fmt.Println(q2, err)
// Finds a record using the correct context of the Coke organization.
q3 := &User{Id: "P3"}
err = ds.Find(CokeOrgCtx, q3)
fmt.Println(q3, err)
// Attempts to find a record using the incorrect context of the Pepsi organization and should error out.
q4 := &User{Id: "P3"}
err = ds.Find(PepsiOrgCtx, q4)
fmt.Printf("err != nil - %t\n", err != nil)
// Deletes a record using the context of the Coke organization and the specified uId.
rowsAffected, err = ds.Delete(CokeOrgCtx, q1)
fmt.Println(rowsAffected, err)
// Deletes a record using the context of the Pepsi organization and the same uId.
rowsAffected, err = ds.Delete(PepsiOrgCtx, q2)
fmt.Println(rowsAffected, err)
// Attempts to delete a record using an invalid context of the Pepsi organization, which should not affect the database.
rowsAffected, err = ds.Delete(PepsiOrgCtx, q4)
fmt.Println(rowsAffected, err)
// Deletes a record using a valid context of the Coke organization.
rowsAffected, err = ds.Delete(CokeOrgCtx, q3)
fmt.Println(rowsAffected, err)
}
1 <nil>
1 <nil>
1 <nil>
[Coke/P1337] Bob: 31 <nil>
[Pepsi/P1337] John: 36 <nil>
[Coke/P3] Pat: 39 <nil>
err != nil - true
1 <nil>
1 <nil>
0 <nil>
1 <nil>
func FromConfig
func FromConfig(l *logrus.Entry, a authorizer.Authorizer, instancer authorizer.Instancer, cfg DBConfig) (d DataStore, err error)
func FromEnv
func FromEnv(l *logrus.Entry, a authorizer.Authorizer, instancer authorizer.Instancer) (d DataStore, err error)
func FromEnvWithDB
func FromEnvWithDB(l *logrus.Entry, a authorizer.Authorizer, instancer authorizer.Instancer, dbName string) (d DataStore, err error)
type Helper
type Helper interface {
FindInTable(ctx context.Context, tableName string, record Record, softDelete bool) (err error)
FindAllInTable(ctx context.Context, tableName string, records interface{}, pagination *Pagination, softDelete bool) error
FindWithFilterInTable(ctx context.Context, tableName string, record Record, records interface{}, pagination *Pagination, softDelete bool) (err error)
GetDBTransaction(ctx context.Context, tableName string, record Record) (tx *gorm.DB, err error)
RegisterHelper(ctx context.Context, roleMapping map[string]dbrole.DbRole, tableName string, record Record) error
}
type Pagination
type Pagination struct {
Offset int
Limit int
SortBy string
}
func DefaultPagination
func DefaultPagination() *Pagination
func GetPagination
func GetPagination(offset int, limit int, sortBy string) *Pagination
func NoPagination
func NoPagination() *Pagination
type Record
type Record interface {
}
func GetRecordInstanceFromSlice(x interface{}) Record
type TenancyInfo
type TenancyInfo struct {
DbRole dbrole.DbRole
InstanceId string
OrgId string
}
type TestHelper
type TestHelper interface {
DropTables(records ...Record) error // Drop DB tables by records
Truncate(tableNames ...string) error // Truncates DB tables
TruncateCascade(cascade bool, tableNames ...string) error // Truncates DB tables, with an option to truncate them in a cascading fashion
HasTable(tableName string) (bool, error) // Checks if DB table exists
}
import "github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/dbrole"
Corresponding *INSTANCE_* roles access is determined by the Instancer's configuration, allowing it to access records exclusively with a specific instance.
- `TENANT_INSTANCE_READER` - has read access to its tenant instance's data
- `INSTANCE_READER` - has read access to specific instance data
- `TENANT_INSTANCE_WRITER` - has read & write access to its tenant instance's data
- `INSTANCE_WRITER` - has read & write access to specific instance data
DAL allows to map a user's service role to the DB role that will be used for that user. If a user has multiple service roles which map to several DB roles, the DB role with the most extensive privileges will be used (see `DbRoles()` for reference to ordered list of DbRoles.
type DbRole
DbRole Database roles/users.
type DbRole string
const (
// NO_ROLE DB Roles.
NO_ROLE DbRole = ""
TENANT_INSTANCE_READER DbRole = "tenant_instance_reader"
TENANT_READER DbRole = "tenant_reader"
INSTANCE_READER DbRole = "instance_reader"
READER DbRole = "reader"
TENANT_INSTANCE_WRITER DbRole = "tenant_instance_writer"
TENANT_WRITER DbRole = "tenant_writer"
INSTANCE_WRITER DbRole = "instance_writer"
WRITER DbRole = "writer"
MAIN DbRole = "main"
)
func Max
func Max(dbRoles []DbRole) DbRole
func Min
func Min(dbRoles []DbRole) DbRole
func (DbRole) GetRoleWithInstancer
func (dbRole DbRole) GetRoleWithInstancer() DbRole
Map roles to instancer based when Instancer is set. Useful for backward compatibility when role Mapping do not reference *INSTANCE* roles, but an Instancer is configured to limit the access to an instance.
func (DbRole) IsDbRoleInstanceScoped
func (dbRole DbRole) IsDbRoleInstanceScoped() bool
func (DbRole) IsDbRoleTenantScoped
func (dbRole DbRole) IsDbRoleTenantScoped() bool
type DbRoleSlice
type DbRoleSlice []DbRole // Needed for sorting records
func DbRoles
func DbRoles() DbRoleSlice
Returns *Ordered* slice of DbRoles. A reader role is always considered to have fewer permissions than a writer role. and a tenant-specific reader/writer role is always considered to have fewer permissions, than a non-tenant specific reader/writer role, respectively.
func (DbRoleSlice) Len
func (a DbRoleSlice) Len() int
func (DbRoleSlice) Less
func (a DbRoleSlice) Less(i, j int) bool
Returns true if the first role has fewer permissions than the second role, and true if the two roles are the same or the second role has more permissions.
func (DbRoleSlice) Swap
func (a DbRoleSlice) Swap(i, j int)
import "github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/errors"
- Constants
- Variables
- type DbError
- func (e *DbError) Error() string
- func (e *DbError) Is(target error) bool
- func (e *DbError) Unwrap() error
- func (e *DbError) With(msg string) *DbError
- func (e *DbError) WithContext(ctx context.Context) *DbError
- func (e *DbError) WithMap(kvMap map[ErrorContextKey]string) *DbError
- func (e *DbError) WithValue(key ErrorContextKey, value string) *DbError
- func (e *DbError) Wrap(err error) *DbError
- type ErrorContextKey
const (
ENV_VAR = ErrorContextKey("EnvVar")
VALUE = ErrorContextKey("Value")
DB_ADMIN_USERNAME = ErrorContextKey("dbAdminUsername")
DB_HOST = ErrorContextKey("dbHost")
DB_NAME = ErrorContextKey("dbName")
DB_PORT = ErrorContextKey("dbPort")
DB_USERNAME = ErrorContextKey("dbUsername")
PORT_NUMBER = ErrorContextKey("PortNumber")
SQL_STMT = ErrorContextKey("SqlStmt")
SSL_MODE = ErrorContextKey("sslMode")
TABLE_NAME = ErrorContextKey("TableName")
TYPE = ErrorContextKey("Type")
DB_ROLE = ErrorContextKey("DbRole")
)
var (
ErrBaseDb = &DbError{}
ErrMissingRoleMapping = ErrBaseDb.With("Missing role mapping for DB table")
ErrNotPtrToStructSlice = ErrBaseDb.With("Argument is invalid")
ErrInvalidPortNumber = ErrBaseDb.With("Port # is invalid")
ErrMissingEnvVar = ErrBaseDb.With("An environment variable is missing or empty")
ErrMissingOrgId = ErrBaseDb.With("OrgId is missing in context")
ErrConnectingToDb = ErrBaseDb.With("Failed to establish a connection with database")
ErrExecutingSqlStmt = ErrBaseDb.With("SQL statement could not be executed")
ErrRegisteringStruct = ErrBaseDb.With("Registration of a struct with DAL failed")
ErrStartingTx = ErrBaseDb.With("Failed to start a transaction")
ErrCommittingTx = ErrBaseDb.With("Failed to commit a transaction")
ErrRevisionConflict = ErrBaseDb.With("Blocking update due to outdated revision")
ErrMarshalling = ErrBaseDb.With("Cannot marshal proto message to binary")
ErrUnmarshalling = ErrBaseDb.With("Cannot unmarshal binary to proto message")
ErrOperationNotAllowed = ErrBaseDb.With("Not authorized to perform the operation on other's data")
ErrFetchingMetadata = ErrBaseDb.With("Error fetching metadata from GRPC context")
ErrTableDoesNotExist = ErrBaseDb.With("Table does not exist")
ErrRecordNotFound = ErrBaseDb.With("Unable to locate record")
ErrNotPtrToStruct = ErrBaseDb.With("PointerToStruct expected, invalid type provided")
ErrAuthContext = ErrBaseDb.With("Error extracting authContext from context")
ErrNoAuthContext = ErrBaseDb.With("Permission denied because authContext is missing")
ErrNoUserContext = ErrBaseDb.With("Permission denied because userInformation is missing")
ErrUserNotAuthorized = ErrBaseDb.With("User is not authorized to access this API")
ErrMissingInstanceId = ErrBaseDb.With("Instance ID is not configured in the context")
ErrMarkingEnforcementFailed = ErrBaseDb.With("Failed to mark resource's enforcement status")
ErrGettingRealizationStatus = ErrBaseDb.With("Failed to get resource's realization status")
ErrResourceStillExists = ErrBaseDb.With("Resource still exists")
)
type DbError
type DbError struct {
// contains filtered or unexported fields
}
func (*DbError) Error
func (e *DbError) Error() string
func (*DbError) Is
func (e *DbError) Is(target error) bool
func (*DbError) Unwrap
func (e *DbError) Unwrap() error
func (*DbError) With
func (e *DbError) With(msg string) *DbError
func (*DbError) WithContext
func (e *DbError) WithContext(ctx context.Context) *DbError
func (*DbError) WithMap
func (e *DbError) WithMap(kvMap map[ErrorContextKey]string) *DbError
func (*DbError) WithValue
func (e *DbError) WithValue(key ErrorContextKey, value string) *DbError
func (*DbError) Wrap
func (e *DbError) Wrap(err error) *DbError
type ErrorContextKey
type ErrorContextKey string
func (ErrorContextKey) String
func (c ErrorContextKey) String() string
import "github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/protostore"
This package exposes interface [pkg.protostore.ProtoStore] to the consumer, which is a wrapper around [pkg.datastore.DataStore] interface and is used specifically to persist Protobuf messages. Just as with [pkg.datastore.DataStore], Protobuf messages can be persisted with revisioning and multi-tenancy support along with `CreatedAt` and `UpdatedAt` timestamps. Tombstone Delete or soft deletes are supported with `DeletedAt` struct field. Use Delete to remove any records that are soft deleted but still in database
- func FromBytes(bytes []byte, message proto.Message) error
- func ToBytes(message proto.Message) ([]byte, error)
- type Metadata
- type ProtoStore
- type ProtoStoreMsg
- type ProtobufDataStore
- func (p ProtobufDataStore) DeleteById(ctx context.Context, id string, msg proto.Message) (int64, error)
- func (p ProtobufDataStore) DropTables(msgs ...proto.Message) error
- func (p ProtobufDataStore) FindAll(ctx context.Context, msgs interface{}, pagination *datastore.Pagination) (metadataMap map[string]Metadata, err error)
- func (p ProtobufDataStore) FindAllAsMap(ctx context.Context, msgsMap interface{}, pagination *datastore.Pagination) (metadataMap map[string]Metadata, err error)
- func (p ProtobufDataStore) FindAllIncludingSoftDeleted(ctx context.Context, msgs interface{}, pagination *datastore.Pagination) (metadataMap map[string]Metadata, err error)
- func (p ProtobufDataStore) FindById(ctx context.Context, id string, msg proto.Message, metadata *Metadata) error
- func (p ProtobufDataStore) FindByIdIncludingSoftDeleted(ctx context.Context, id string, msg proto.Message, metadata *Metadata) error
- func (p ProtobufDataStore) GetAuthorizer() authorizer.Authorizer
- func (p ProtobufDataStore) GetDataStore() datastore.DataStore
- func (p ProtobufDataStore) GetMetadata(ctx context.Context, id string, msg proto.Message) (md Metadata, err error)
- func (p ProtobufDataStore) GetRevision(ctx context.Context, id string, msg proto.Message) (int64, error)
- func (p ProtobufDataStore) GetSoftDeletedMetadata(ctx context.Context, id string, msg proto.Message) (md Metadata, err error)
- func (p ProtobufDataStore) Insert(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
- func (p ProtobufDataStore) InsertWithMetadata(ctx context.Context, id string, msg proto.Message, metadata Metadata) (rowsAffected int64, md Metadata, err error)
- func (p ProtobufDataStore) MsgToFilter(ctx context.Context, id string, msg proto.Message) (pMsg *ProtoStoreMsg, err error)
- func (p ProtobufDataStore) MsgToPersist(ctx context.Context, id string, msg proto.Message, md Metadata) (pMsg *ProtoStoreMsg, err error)
- func (p ProtobufDataStore) Register(ctx context.Context, roleMapping map[string]dbrole.DbRole, msgs ...proto.Message) error
- func (p ProtobufDataStore) SoftDeleteById(ctx context.Context, id string, msg proto.Message) (int64, Metadata, error)
- func (p ProtobufDataStore) Update(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
- func (p ProtobufDataStore) UpdateWithMetadata(ctx context.Context, id string, msg proto.Message, metadata Metadata) (rowsAffected int64, md Metadata, err error)
- func (p ProtobufDataStore) Upsert(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
- func (p ProtobufDataStore) UpsertWithMetadata(ctx context.Context, id string, msg proto.Message, metadata Metadata) (rowsAffected int64, md Metadata, err error)
func FromBytes
func FromBytes(bytes []byte, message proto.Message) error
func ToBytes
func ToBytes(message proto.Message) ([]byte, error)
type Metadata
type Metadata struct {
Id string
InstanceId string
ParentId string
Revision int64
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt
}
func MetadataFrom
func MetadataFrom(protoStoreMsg ProtoStoreMsg) Metadata
type ProtoStore
type ProtoStore interface {
Register(ctx context.Context, roleMapping map[string]dbrole.DbRole, msgs ...proto.Message) error
Insert(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
Update(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
Upsert(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
FindById(ctx context.Context, id string, msg proto.Message, metadata *Metadata) error
FindByIdIncludingSoftDeleted(ctx context.Context, id string, msg proto.Message, metadata *Metadata) error
FindAll(ctx context.Context, msgs interface{}, pagination *datastore.Pagination) (metadataMap map[string]Metadata, err error)
FindAllIncludingSoftDeleted(ctx context.Context, msgs interface{}, pagination *datastore.Pagination) (metadataMap map[string]Metadata, err error)
FindAllAsMap(ctx context.Context, msgsMap interface{}, pagination *datastore.Pagination) (metadataMap map[string]Metadata, err error)
SoftDeleteById(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
DeleteById(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, err error)
InsertWithMetadata(ctx context.Context, id string, msg proto.Message, metadata Metadata) (rowsAffected int64, md Metadata, err error)
UpdateWithMetadata(ctx context.Context, id string, msg proto.Message, metadata Metadata) (rowsAffected int64, md Metadata, err error)
UpsertWithMetadata(ctx context.Context, id string, msg proto.Message, metadata Metadata) (rowsAffected int64, md Metadata, err error)
GetMetadata(ctx context.Context, id string, msg proto.Message) (md Metadata, err error)
GetSoftDeletedMetadata(ctx context.Context, id string, msg proto.Message) (md Metadata, err error)
GetRevision(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, err error)
MsgToFilter(ctx context.Context, id string, msg proto.Message) (pMsg *ProtoStoreMsg, err error)
MsgToPersist(ctx context.Context, id string, msg proto.Message, md Metadata) (pMsg *ProtoStoreMsg, err error)
GetDataStore() datastore.DataStore
GetAuthorizer() authorizer.Authorizer
DropTables(msgs ...proto.Message) error
}
Example
package main
import (
"context"
"fmt"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/authorizer"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/datastore"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/dbrole"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/logutils"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/protostore"
"github.com/vmware-labs/multi-tenant-persistence-for-saas/test/pb"
)
func main() {
// Initialize protostore with proper logger, authorizer and datastore
myLogger := logutils.GetCompLogger()
mdAuthorizer := &authorizer.MetadataBasedAuthorizer{}
myDatastore, _ := datastore.FromEnvWithDB(myLogger, mdAuthorizer, nil, "ExampleProtoStore")
defer myDatastore.Reset()
ctx := mdAuthorizer.GetAuthContext("Coke", "service_admin")
myProtostore := protostore.GetProtoStore(myLogger, myDatastore)
// Register protobufs with proper roleMappings
roleMappingForMemory := map[string]dbrole.DbRole{
"service_auditor": dbrole.READER,
"service_admin": dbrole.WRITER,
}
_ = myProtostore.Register(context.TODO(), roleMappingForMemory, &pb.Memory{})
// Store protobuf message using Upsert
id := "0001"
memory := &pb.Memory{
Brand: "Samsung",
Size: 32,
Speed: 2933,
Type: "DDR4",
}
rowsAffected, metadata, err := myProtostore.Upsert(ctx, id, memory)
fmt.Println("After Upsert::", "revision:", metadata.Revision, "rowsAffected:", rowsAffected, "err:", err)
// Retrieve protobuf message using ID
memory = &pb.Memory{}
err = myProtostore.FindById(ctx, id, memory, &metadata)
fmt.Println("FindById::", "revision:", metadata.Revision, "rowsAffected:", rowsAffected, "err:", err)
// Update the protobuf message with metadata (existing revision)
memory.Speed++
rowsAffected, metadata, err = myProtostore.UpdateWithMetadata(ctx, id, memory, metadata)
fmt.Println("After UpdateWithMetadata::", "revision:", metadata.Revision, "rowsAffected:", rowsAffected, "err:", err)
// Retrieve all the protobuf messages
queryResults := make([]*pb.Memory, 0)
metadataMap, err := myProtostore.FindAll(ctx, &queryResults, datastore.NoPagination())
fmt.Println("FindAll::", "revision:", metadataMap[id].Revision, "rowsFound:", len(queryResults), "err:", err)
// Delete the protobuf (soft delete)
rowsAffected, _, err = myProtostore.SoftDeleteById(ctx, id, &pb.Memory{})
fmt.Println("SoftDeleteById::", "rowsAffected:", rowsAffected, "err:", err)
// Delete the protobuf (full delete)
rowsAffected, err = myProtostore.DeleteById(ctx, id, &pb.Memory{})
fmt.Println("DeleteById::", "rowsAffected:", rowsAffected, "err:", err)
}
After Upsert:: revision: 1 rowsAffected: 1 err: <nil>
FindById:: revision: 1 rowsAffected: 1 err: <nil>
After UpdateWithMetadata:: revision: 2 rowsAffected: 1 err: <nil>
FindAll:: revision: 2 rowsFound: 1 err: <nil>
SoftDeleteById:: rowsAffected: 1 err: <nil>
DeleteById:: rowsAffected: 1 err: <nil>
func GetProtoStore
func GetProtoStore(logger *logrus.Entry, ds datastore.DataStore) ProtoStore
type ProtoStoreMsg
type ProtoStoreMsg struct {
Id string `gorm:"primaryKey" json:"id"`
Msg []byte `json:"-"`
ParentId string `json:"parent_id,omitempty"`
Revision int64 `json:"revision"`
OrgId string `gorm:"primaryKey" json:"org_id"`
InstanceId string `gorm:"primaryKey" json:"instance_id,omitempty"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
DeletedAt gorm.DeletedAt `json:"-"`
XTableName string `gorm:"-" json:"x_table_name"`
}
func (*ProtoStoreMsg) String
func (msg *ProtoStoreMsg) String() string
func (*ProtoStoreMsg) TableName
func (msg *ProtoStoreMsg) TableName() string
type ProtobufDataStore
type ProtobufDataStore struct {
// contains filtered or unexported fields
}
func (ProtobufDataStore) DeleteById
func (p ProtobufDataStore) DeleteById(ctx context.Context, id string, msg proto.Message) (int64, error)
func (ProtobufDataStore) DropTables
func (p ProtobufDataStore) DropTables(msgs ...proto.Message) error
func (ProtobufDataStore) FindAll
func (p ProtobufDataStore) FindAll(ctx context.Context, msgs interface{}, pagination *datastore.Pagination) (metadataMap map[string]Metadata, err error)
FindAll Finds all messages (of the same type as the element of msgs) in Protostore and stores the result in msgs. msgs must be a pointer to a slice of Protobuf structs or a pointer to a slice of pointers to Protobuf structs. It will be modified in-place. Returns a map of Protobuf messages' IDs to their metadata (parent ID & revision).
func (ProtobufDataStore) FindAllAsMap
func (p ProtobufDataStore) FindAllAsMap(ctx context.Context, msgsMap interface{}, pagination *datastore.Pagination) (metadataMap map[string]Metadata, err error)
func (ProtobufDataStore) FindAllIncludingSoftDeleted
func (p ProtobufDataStore) FindAllIncludingSoftDeleted(ctx context.Context, msgs interface{}, pagination *datastore.Pagination) (metadataMap map[string]Metadata, err error)
func (ProtobufDataStore) FindById
func (p ProtobufDataStore) FindById(ctx context.Context, id string, msg proto.Message, metadata *Metadata) error
Finds a Protobuf message by ID. If metadata arg. is non-nil, fills it with the metadata (parent ID & revision) of the Protobuf message that was found.
func (ProtobufDataStore) FindByIdIncludingSoftDeleted
func (p ProtobufDataStore) FindByIdIncludingSoftDeleted(ctx context.Context, id string, msg proto.Message, metadata *Metadata) error
func (ProtobufDataStore) GetAuthorizer
func (p ProtobufDataStore) GetAuthorizer() authorizer.Authorizer
func (ProtobufDataStore) GetDataStore
func (p ProtobufDataStore) GetDataStore() datastore.DataStore
func (ProtobufDataStore) GetMetadata
func (p ProtobufDataStore) GetMetadata(ctx context.Context, id string, msg proto.Message) (md Metadata, err error)
func (ProtobufDataStore) GetRevision
func (p ProtobufDataStore) GetRevision(ctx context.Context, id string, msg proto.Message) (int64, error)
func (ProtobufDataStore) GetSoftDeletedMetadata
func (p ProtobufDataStore) GetSoftDeletedMetadata(ctx context.Context, id string, msg proto.Message) (md Metadata, err error)
func (ProtobufDataStore) Insert
func (p ProtobufDataStore) Insert(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
@DEPRECATED See [InsertWithMetadata].
func (ProtobufDataStore) InsertWithMetadata
func (p ProtobufDataStore) InsertWithMetadata(ctx context.Context, id string, msg proto.Message, metadata Metadata) (rowsAffected int64, md Metadata, err error)
Inserts a new Protobuf record in the DB. Returns, rowsAffected - 0 if insertion fails; 1 otherwise md - metadata of the new Protobuf record err - error that occurred during insertion, if any.
func (ProtobufDataStore) MsgToFilter
func (p ProtobufDataStore) MsgToFilter(ctx context.Context, id string, msg proto.Message) (pMsg *ProtoStoreMsg, err error)
Return the ProtoStoreMsg that can be used for filtering with id/orgId filled up and error that occurred during extraction orgId from context.
func (ProtobufDataStore) MsgToPersist
func (p ProtobufDataStore) MsgToPersist(ctx context.Context, id string, msg proto.Message, md Metadata) (pMsg *ProtoStoreMsg, err error)
Return the serialized ProtoStoreMsg that can be persisted to database and error that occurred during extraction orgId from context, or serialization.
func (ProtobufDataStore) Register
func (p ProtobufDataStore) Register(ctx context.Context, roleMapping map[string]dbrole.DbRole, msgs ...proto.Message) error
func (ProtobufDataStore) SoftDeleteById
func (p ProtobufDataStore) SoftDeleteById(ctx context.Context, id string, msg proto.Message) (int64, Metadata, error)
func (ProtobufDataStore) Update
func (p ProtobufDataStore) Update(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
Update Fetches metadata for the record and updates the Protobuf message. NOTE: Avoid using this method in user-workflows and only in service-to-service workflows when the updates are already ordered by some other service/app.
func (ProtobufDataStore) UpdateWithMetadata
func (p ProtobufDataStore) UpdateWithMetadata(ctx context.Context, id string, msg proto.Message, metadata Metadata) (rowsAffected int64, md Metadata, err error)
Updates an existing Protobuf record in the DB. Returns, rowsAffected - 0 if update fails; 1 otherwise md - metadata of the updated Protobuf record err - error that occurred during update, if any.
func (ProtobufDataStore) Upsert
func (p ProtobufDataStore) Upsert(ctx context.Context, id string, msg proto.Message) (rowsAffected int64, md Metadata, err error)
Upsert Fetches metadata for the record and upserts the Protobuf message. NOTE: Avoid using this method in user-workflows and only in service-to-service workflows when the updates are already ordered by some other service/app.
func (ProtobufDataStore) UpsertWithMetadata
func (p ProtobufDataStore) UpsertWithMetadata(ctx context.Context, id string, msg proto.Message, metadata Metadata) (rowsAffected int64, md Metadata, err error)
Upserts a Protobuf record in the DB (if the record exists, it is updated; if it does not, it is inserted). Returns, rowsAffected - 0 if upsert fails; 1 otherwise md - metadata of the upserted Protobuf record err - error that occurred during upsert, if any.
import "github.com/vmware-labs/multi-tenant-persistence-for-saas/pkg/realization_store"
This package exposes IRealizationStore interface that will be used to support managing statuses of resources being realized at different enforcement points and aggregating it OverallStatus to represent the state of resource.
## Implementation
Two database tables will be used to track realization status of each resource type. The first one, `EnforcementStatus`, stores realization status of a resource at specific enforcement points, where an enforcement point is a workload where a resource is actually realized and where its status can be queried. The second one, `OverallStatus`, stores overall realization status of a resource across all enforcement points. The following ER diagrams show the DB schema for resource as an example.
### ER diagram
```mermaid erDiagram
Resource ||--|| ResourceOverallStatus : "Overall Status"
Resource ||--o{ ResourceEnforcementStatus : "Status Per Enforcement Point"
Resource {
string id
string org_id
byte[] msg
string parent_id
bigint revision
}
ResourceOverallStatus {
string id
string org_id
enum status "DELETION_REALIZED|REALIZED|DELETION_IN_PROGRESS|DELETION_PENDING|IN_PROGRESS|PENDING|ERROR"
string additional_details
created_at timestamptz
updated_at timestamptz
bigint revision
}
ResourceEnforcementStatus {
string id
string org_id
string enforcement_point_id
enum status "DELETION_REALIZED|REALIZED|DELETION_IN_PROGRESS|DELETION_PENDING|IN_PROGRESS|PENDING|ERROR"
string additional_details
created_at timestamptz
updated_at timestamptz
bigint revision
}
```
## Common workflows
The common workflows related to realization status support would be these: **Scenario: A resource is created** End user issues a request to persist an intent for resource _R1_. It needs to be realized at enforcement points _E1_ & _E2_. Consumer(s) would call these methods:
PersistIntent(R1)
MarkEnforcementAsPending(E1, R1)
MarkEnforcementAsPending(E2, R1)
//R1 is successfully realized at E1
MarkEnforcementAsSuccess(E1, R1)
//R1 is successfully realized at E2
MarkEnforcementAsSuccess(E2, R1)
**Scenario: A resource is modified. Its status is queried** End user issues a request to perist a modified intent for resource _R1_. The modified resource needs to be realized at enforcement points _E1_ & _E2_. Consumer(s) would call these methods:
PersistIntent(R1)
MarkEnforcementAsPending(E1, R1)
MarkEnforcementAsPending(E2, R1)
//R1 is successfully realized at E1
MarkEnforcementAsSuccess(E1, R1)
//R1 is successfully realized at E2
MarkEnforcementAsSuccess(E2, R1)
GetOverallStatusWithEnforcementDetails(R1)
**Scenario: A resource is deleted** End user issues a request to delete an intent for resource _R1_. The deleted resource needs to be "unenforced" at enforcement points _E1_ & _E2_. Consumer(s) would call these methods:
Delete(R1)
MarkEnforcementAsDeletionPending(E1, R1)
MarkEnforcementAsDeletionPending(E1, R1)
//R1 is successfully "unenforced" at E1
MarkEnforcementAsDeletionRealized(E1, R1) //TBD
//R1 is successfully "unenforced" at E2
MarkEnforcementAsDeletionRealized(E2, R1) //TBD
//Cleanup mechanism deletes stale status records for deleted resources - TBD
**Scenario: A resource is created but fails to be realized at some enforcement points** End user issues a request to persist an intent for resource _R1_. It needs to be realized at enforcement points _E1_ & _E2_. Consumer(s) would call these methods:
PersistIntent(R1)
MarkEnforcementAsPending(E1, R1)
MarkEnforcementAsPending(E2, R1)
//R1 fails to be realized at E1 due to error err
MarkEnforcementAsError(E1, err, R1)
//R1 is successfully realized at E2
MarkEnforcementAsSuccess(E2, R1)
**Scenario: A new enforcement point is added** An enforcement point _E3_ is added (by end-user or an admin). Resources _R1_ and _R2_ need to be enforced on it. Consumer(s) would call these methods:
//Enforcement point is added
MarkEnforcementAsPending(E3, R1, R2)
//R1 is successfully realized at E3
MarkEnforcementAsSuccess(E3, R1)
//R2 is successfully realized at E3
MarkEnforcementAsSuccess(E3, R2)
**Scenario: An enforcement point is removed** An enforcement point _E3_ is added (by end-user or an admin). Resources _R1_ and _R2_ need to be "unenforced" on it. Consumer(s) would call these methods:
//Enforcement point is removed
MarkEnforcementAsDeletionRealized(E3, R1, R2) //TBD
- Constants
- func GetEnforcementStatusTableName(msg proto.Message) string
- func GetOverallStatusTableName(msg proto.Message) string
- type EnforcementStatus
- type IRealizationStore
- type OverallStatus
- type ProtobufWithMetadata
- type Status
Constants for logger field names & values.
const (
ORG_ID = "orgId"
RESOURCE_ID = "resourceId"
ENFORCEMENT_POINT = "enforcementPoint"
ALL = "*"
)
const (
RESOURCE = "resource"
OVERALL_STATUS = "overall status"
ENFORCEMENT_STATUS = "enforcement status"
ENFORCEMENT = "enforcement"
INTENT = "intent"
PERSISTING = "Persisting"
SETTING = "Setting"
RESETTING = "Resetting"
SOFT_DELETING = "Soft-Deleting"
DELETING = "Deleting"
PURGING = "Purging"
FETCHING = "Fetching"
REGISTERING = "Registering"
MARKING = "Marking"
STARTED = "..."
FAILED = "failed: "
ERRORED = "errored: "
FINISHED = "finished."
AS_PENDING = "as pending"
AS_SUCCESS = "as success"
AS_ERROR = "as error"
AS_DELETION_PENDING = "as deletion pending"
AS_DELETION_REALIZED = "as deletion realized"
)
const (
ADDITIONAL_DETAILS_LENGTH_CAP = 1024
)
func GetEnforcementStatusTableName(msg proto.Message) string
func GetOverallStatusTableName(msg proto.Message) string
type EnforcementStatus
type EnforcementStatus struct {
Id string `gorm:"primaryKey"`
OrgId string `gorm:"primaryKey"`
EnforcementPointId string `gorm:"primaryKey"`
RealizationStatus Status
AdditionalDetails string
Revision int64 `gorm:"column:resource_revision"`
XTableName string `gorm:"-"`
CreatedAt time.Time
UpdatedAt time.Time
}
func GetModelEnforcementStatusRecord(resource *ProtobufWithMetadata, orgId string) *EnforcementStatus
func GetModelEnforcementStatusRecordWithoutRevision(resource *ProtobufWithMetadata, orgId string) *EnforcementStatus
func (*EnforcementStatus) TableName
func (e *EnforcementStatus) TableName() string
type IRealizationStore
type IRealizationStore interface {
Register(ctx context.Context, roleMapping map[string]dbrole.DbRole, msgs ...proto.Message) error
FindById(ctx context.Context, id string, resource *ProtobufWithMetadata) error
PersistIntent(ctx context.Context, resource *ProtobufWithMetadata) (rowsAffected int64, metadata protostore.Metadata, err error)
MarkEnforcementAsPending(ctx context.Context, enforcementPoint string, resources ...*ProtobufWithMetadata) error
MarkEnforcementAsInProgress(ctx context.Context, enforcementPoint string, resources ...*ProtobufWithMetadata) error
MarkEnforcementAsSuccess(ctx context.Context, enforcementPoint string, resources ...*ProtobufWithMetadata) error
MarkEnforcementAsError(ctx context.Context, enforcementPoint string, errStr string, resources ...*ProtobufWithMetadata) error
SoftDelete(ctx context.Context, resource *ProtobufWithMetadata) (rowsAffected int64, metadata protostore.Metadata, err error)
Delete(ctx context.Context, resource *ProtobufWithMetadata) (rowsAffected int64, err error)
Purge(ctx context.Context, resource *ProtobufWithMetadata) (rowsAffected int64, err error)
MarkEnforcementAsDeletionPending(ctx context.Context, enforcementPoint string, resources ...*ProtobufWithMetadata) error
MarkEnforcementAsDeletionInProgress(ctx context.Context, enforcementPoint string, resources ...*ProtobufWithMetadata) error
MarkEnforcementAsDeletionRealized(ctx context.Context, enforcementPoint string, resources ...*ProtobufWithMetadata) error
MarkEnforcementAsDeletionError(ctx context.Context, enforcementPoint string, errStr string, resources ...*ProtobufWithMetadata) error
GetOverallStatus(ctx context.Context, resource *ProtobufWithMetadata) (Status, error)
GetOverallStatusWithEnforcementDetails(ctx context.Context, resource *ProtobufWithMetadata) (Status, map[string]Status, error)
GetEnforcementStatusMap(ctx context.Context, resource *ProtobufWithMetadata) (overallStatus OverallStatus, enforcementStatusMap map[string]EnforcementStatus, err error)
// To handle error scenarios and for intrumentation cases
PurgeStaleStatusRecords(ctx context.Context, resource *ProtobufWithMetadata) (rowsAffected int64, err error)
PurgeStaleEnforcementStatusRecords(ctx context.Context, resource *ProtobufWithMetadata, enforcementStatusRecordMap map[string]EnforcementStatus) (rowsAffected int64, err error)
}
func GetRealizationStore
func GetRealizationStore(d datastore.DataStore, p protostore.ProtoStore, logger *logrus.Entry) IRealizationStore
type OverallStatus
type OverallStatus struct {
Id string `gorm:"primaryKey"`
OrgId string `gorm:"primaryKey"`
RealizationStatus Status
AdditionalDetails string
Revision int64 `gorm:"column:resource_revision"`
XTableName string `gorm:"-"`
CreatedAt time.Time
UpdatedAt time.Time
}
func GetModelOverallStatusRecord(resource *ProtobufWithMetadata, orgId string) *OverallStatus
func (*OverallStatus) TableName
func (o *OverallStatus) TableName() string
type ProtobufWithMetadata
type ProtobufWithMetadata struct {
proto.Message
protostore.Metadata
}
type Status
type Status int
The values are ordered in th priority order, where the highest value is set as overall status for given resource, from the list of enforcement statuses for that resource NOTE: Do not reorder or change the values of the statuses.
const (
UNKNOWN Status = 0
DELETION_REALIZED Status = 100
REALIZED Status = 200
DELETION_IN_PROGRESS Status = 300
DELETION_PENDING Status = 400
IN_PROGRESS Status = 600
PENDING Status = 800
ERROR Status = 1000
)
func (Status) String
func (i Status) String() string
Generated by gomarkdoc