mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-07 19:27:41 +00:00
feat: add tenant column to eventstore (#3314)
* feat: add tenant column to eventstore * feat: read tenant from context on push and filter * Update 07_events_table.sql * pass tenant to queryFactory * fix some query tests * init in tests * add missing sql files Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
parent
5463244376
commit
5132ebe07c
@ -5,12 +5,13 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/zitadel/internal/database"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
//sql import
|
//sql import
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New() *cobra.Command {
|
func New() *cobra.Command {
|
||||||
@ -33,9 +34,9 @@ The user provided by flags needs priviledge to
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := initialise(config,
|
if err := initialise(config,
|
||||||
verifyUser(config.Database),
|
VerifyUser(config.Database.User.Username, config.Database.User.Password),
|
||||||
verifyDatabase(config.Database),
|
VerifyDatabase(config.Database.Database),
|
||||||
verifyGrant(config.Database),
|
VerifyGrant(config.Database.Database, config.Database.User.Username),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -55,12 +56,18 @@ func initialise(config Config, steps ...func(*sql.DB) error) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = Initialise(db, steps...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Initialise(db *sql.DB, steps ...func(*sql.DB) error) error {
|
||||||
for _, step := range steps {
|
for _, step := range steps {
|
||||||
if err = step(db); err != nil {
|
if err := step(db); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
return db.Close()
|
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,14 @@ CREATE TABLE eventstore.events (
|
|||||||
, editor_user TEXT NOT NULL
|
, editor_user TEXT NOT NULL
|
||||||
, editor_service TEXT NOT NULL
|
, editor_service TEXT NOT NULL
|
||||||
, resource_owner TEXT NOT NULL
|
, resource_owner TEXT NOT NULL
|
||||||
|
, tenant TEXT
|
||||||
|
|
||||||
, PRIMARY KEY (event_sequence DESC) USING HASH WITH BUCKET_COUNT = 10
|
, PRIMARY KEY (event_sequence DESC) USING HASH WITH BUCKET_COUNT = 10
|
||||||
, INDEX agg_type_agg_id (aggregate_type, aggregate_id)
|
, INDEX agg_type_agg_id (aggregate_type, aggregate_id)
|
||||||
, INDEX agg_type (aggregate_type)
|
, INDEX agg_type (aggregate_type)
|
||||||
, INDEX agg_type_seq (aggregate_type, event_sequence DESC)
|
, INDEX agg_type_seq (aggregate_type, event_sequence DESC)
|
||||||
STORING (id, event_type, aggregate_id, aggregate_version, previous_aggregate_sequence, creation_date, event_data, editor_user, editor_service, resource_owner, previous_aggregate_type_sequence)
|
STORING (id, event_type, aggregate_id, aggregate_version, previous_aggregate_sequence, creation_date, event_data, editor_user, editor_service, resource_owner, tenant, previous_aggregate_type_sequence)
|
||||||
, INDEX max_sequence (aggregate_type, aggregate_id, event_sequence DESC)
|
, INDEX max_sequence (aggregate_type, aggregate_id, event_sequence DESC)
|
||||||
, CONSTRAINT previous_sequence_unique UNIQUE (previous_aggregate_sequence DESC)
|
, CONSTRAINT previous_sequence_unique UNIQUE (previous_aggregate_sequence DESC)
|
||||||
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence)
|
, CONSTRAINT prev_agg_type_seq_unique UNIQUE(previous_aggregate_type_sequence)
|
||||||
)
|
)
|
||||||
|
1
cmd/admin/initialise/sql/10_system_sequence.sql
Normal file
1
cmd/admin/initialise/sql/10_system_sequence.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
CREATE SEQUENCE eventstore.system_seq
|
5
cmd/admin/initialise/sql/11_unique_constraints_table.sql
Normal file
5
cmd/admin/initialise/sql/11_unique_constraints_table.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE eventstore.unique_constraints (
|
||||||
|
unique_type TEXT,
|
||||||
|
unique_field TEXT,
|
||||||
|
PRIMARY KEY (unique_type, unique_field)
|
||||||
|
)
|
@ -5,7 +5,6 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/database"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@ -36,16 +35,16 @@ The user provided by flags needs priviledge to
|
|||||||
if err := viper.Unmarshal(&config); err != nil {
|
if err := viper.Unmarshal(&config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return initialise(config, verifyDatabase(config.Database))
|
return initialise(config, VerifyDatabase(config.Database.Database))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyDatabase(config database.Config) func(*sql.DB) error {
|
func VerifyDatabase(database string) func(*sql.DB) error {
|
||||||
return func(db *sql.DB) error {
|
return func(db *sql.DB) error {
|
||||||
return verify(db,
|
return verify(db,
|
||||||
exists(searchDatabase, config.Database),
|
exists(searchDatabase, database),
|
||||||
exec(fmt.Sprintf(databaseStmt, config.Database)),
|
exec(fmt.Sprintf(databaseStmt, database)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,12 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_verifyDB(t *testing.T) {
|
func Test_verifyDB(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
db db
|
db db
|
||||||
config database.Config
|
database string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -21,10 +19,8 @@ func Test_verifyDB(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "exists fails",
|
name: "exists fails",
|
||||||
args: args{
|
args: args{
|
||||||
db: prepareDB(t, expectQueryErr("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", sql.ErrConnDone, "zitadel")),
|
db: prepareDB(t, expectQueryErr("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", sql.ErrConnDone, "zitadel")),
|
||||||
config: database.Config{
|
database: "zitadel",
|
||||||
Database: "zitadel",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: sql.ErrConnDone,
|
targetErr: sql.ErrConnDone,
|
||||||
},
|
},
|
||||||
@ -35,9 +31,7 @@ func Test_verifyDB(t *testing.T) {
|
|||||||
expectExists("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", false, "zitadel"),
|
expectExists("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", false, "zitadel"),
|
||||||
expectExec("CREATE DATABASE zitadel", sql.ErrTxDone),
|
expectExec("CREATE DATABASE zitadel", sql.ErrTxDone),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
database: "zitadel",
|
||||||
Database: "zitadel",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: sql.ErrTxDone,
|
targetErr: sql.ErrTxDone,
|
||||||
},
|
},
|
||||||
@ -48,9 +42,7 @@ func Test_verifyDB(t *testing.T) {
|
|||||||
expectExists("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", false, "zitadel"),
|
expectExists("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", false, "zitadel"),
|
||||||
expectExec("CREATE DATABASE zitadel", nil),
|
expectExec("CREATE DATABASE zitadel", nil),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
database: "zitadel",
|
||||||
Database: "zitadel",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: nil,
|
targetErr: nil,
|
||||||
},
|
},
|
||||||
@ -60,16 +52,14 @@ func Test_verifyDB(t *testing.T) {
|
|||||||
db: prepareDB(t,
|
db: prepareDB(t,
|
||||||
expectExists("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", true, "zitadel"),
|
expectExists("SELECT EXISTS(SELECT database_name FROM [show databases] WHERE database_name = $1)", true, "zitadel"),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
database: "zitadel",
|
||||||
Database: "zitadel",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: nil,
|
targetErr: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if err := verifyDatabase(tt.args.config)(tt.args.db.db); !errors.Is(err, tt.targetErr) {
|
if err := VerifyDatabase(tt.args.database)(tt.args.db.db); !errors.Is(err, tt.targetErr) {
|
||||||
t.Errorf("verifyDB() error = %v, want: %v", err, tt.targetErr)
|
t.Errorf("verifyDB() error = %v, want: %v", err, tt.targetErr)
|
||||||
}
|
}
|
||||||
if err := tt.args.db.mock.ExpectationsWereMet(); err != nil {
|
if err := tt.args.db.mock.ExpectationsWereMet(); err != nil {
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/zitadel/internal/database"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@ -31,17 +30,17 @@ Prereqesits:
|
|||||||
if err := viper.Unmarshal(&config); err != nil {
|
if err := viper.Unmarshal(&config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return initialise(config, verifyGrant(config.Database))
|
return initialise(config, VerifyGrant(config.Database.Database, config.Database.User.Username))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyGrant(config database.Config) func(*sql.DB) error {
|
func VerifyGrant(database, username string) func(*sql.DB) error {
|
||||||
return func(db *sql.DB) error {
|
return func(db *sql.DB) error {
|
||||||
logging.WithFields("user", config.Username).Info("verify grant")
|
logging.WithFields("user", username).Info("verify grant")
|
||||||
return verify(db,
|
return verify(db,
|
||||||
exists(fmt.Sprintf(searchGrant, config.Database), config.Username),
|
exists(fmt.Sprintf(searchGrant, database), username),
|
||||||
exec(fmt.Sprintf(grantStmt, config.Database, config.Username)),
|
exec(fmt.Sprintf(grantStmt, database, username)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,13 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_verifyGrant(t *testing.T) {
|
func Test_verifyGrant(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
db db
|
db db
|
||||||
config database.Config
|
database string
|
||||||
|
username string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -21,13 +20,9 @@ func Test_verifyGrant(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "exists fails",
|
name: "exists fails",
|
||||||
args: args{
|
args: args{
|
||||||
db: prepareDB(t, expectQueryErr("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", sql.ErrConnDone, "zitadel-user")),
|
db: prepareDB(t, expectQueryErr("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", sql.ErrConnDone, "zitadel-user")),
|
||||||
config: database.Config{
|
database: "zitadel",
|
||||||
Database: "zitadel",
|
username: "zitadel-user",
|
||||||
User: database.User{
|
|
||||||
Username: "zitadel-user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: sql.ErrConnDone,
|
targetErr: sql.ErrConnDone,
|
||||||
},
|
},
|
||||||
@ -38,12 +33,8 @@ func Test_verifyGrant(t *testing.T) {
|
|||||||
expectExists("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", false, "zitadel-user"),
|
expectExists("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", false, "zitadel-user"),
|
||||||
expectExec("GRANT ALL ON DATABASE zitadel TO zitadel-user", sql.ErrTxDone),
|
expectExec("GRANT ALL ON DATABASE zitadel TO zitadel-user", sql.ErrTxDone),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
database: "zitadel",
|
||||||
Database: "zitadel",
|
username: "zitadel-user",
|
||||||
User: database.User{
|
|
||||||
Username: "zitadel-user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: sql.ErrTxDone,
|
targetErr: sql.ErrTxDone,
|
||||||
},
|
},
|
||||||
@ -54,12 +45,8 @@ func Test_verifyGrant(t *testing.T) {
|
|||||||
expectExists("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", false, "zitadel-user"),
|
expectExists("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", false, "zitadel-user"),
|
||||||
expectExec("GRANT ALL ON DATABASE zitadel TO zitadel-user", nil),
|
expectExec("GRANT ALL ON DATABASE zitadel TO zitadel-user", nil),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
database: "zitadel",
|
||||||
Database: "zitadel",
|
username: "zitadel-user",
|
||||||
User: database.User{
|
|
||||||
Username: "zitadel-user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: nil,
|
targetErr: nil,
|
||||||
},
|
},
|
||||||
@ -69,20 +56,16 @@ func Test_verifyGrant(t *testing.T) {
|
|||||||
db: prepareDB(t,
|
db: prepareDB(t,
|
||||||
expectExists("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", true, "zitadel-user"),
|
expectExists("SELECT EXISTS(SELECT * FROM [SHOW GRANTS ON DATABASE zitadel] where grantee = $1 AND privilege_type = 'ALL'", true, "zitadel-user"),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
database: "zitadel",
|
||||||
Database: "zitadel",
|
username: "zitadel-user",
|
||||||
User: database.User{
|
|
||||||
Username: "zitadel-user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: nil,
|
targetErr: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if err := verifyGrant(tt.args.config)(tt.args.db.db); !errors.Is(err, tt.targetErr) {
|
if err := VerifyGrant(tt.args.database, tt.args.username)(tt.args.db.db); !errors.Is(err, tt.targetErr) {
|
||||||
t.Errorf("verifyGrant() error = %v, want: %v", err, tt.targetErr)
|
t.Errorf("VerifyGrant() error = %v, want: %v", err, tt.targetErr)
|
||||||
}
|
}
|
||||||
if err := tt.args.db.mock.ExpectationsWereMet(); err != nil {
|
if err := tt.args.db.mock.ExpectationsWereMet(); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/zitadel/internal/database"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
@ -35,17 +34,17 @@ The user provided by flags needs priviledge to
|
|||||||
if err := viper.Unmarshal(&config); err != nil {
|
if err := viper.Unmarshal(&config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return initialise(config, verifyUser(config.Database))
|
return initialise(config, VerifyUser(config.Database.User.Username, config.Database.User.Password))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyUser(config database.Config) func(*sql.DB) error {
|
func VerifyUser(username, password string) func(*sql.DB) error {
|
||||||
return func(db *sql.DB) error {
|
return func(db *sql.DB) error {
|
||||||
logging.WithFields("username", config.Username).Info("verify user")
|
logging.WithFields("username", username).Info("verify user")
|
||||||
return verify(db,
|
return verify(db,
|
||||||
exists(searchUser, config.Username),
|
exists(searchUser, username),
|
||||||
exec(createUserStmt, config.Username, &sql.NullString{String: config.Password, Valid: config.Password != ""}),
|
exec(createUserStmt, username, &sql.NullString{String: password, Valid: password != ""}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,13 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_verifyUser(t *testing.T) {
|
func Test_verifyUser(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
db db
|
db db
|
||||||
config database.Config
|
username string
|
||||||
|
password string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -21,13 +20,9 @@ func Test_verifyUser(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "exists fails",
|
name: "exists fails",
|
||||||
args: args{
|
args: args{
|
||||||
db: prepareDB(t, expectQueryErr("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", sql.ErrConnDone, "zitadel-user")),
|
db: prepareDB(t, expectQueryErr("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", sql.ErrConnDone, "zitadel-user")),
|
||||||
config: database.Config{
|
username: "zitadel-user",
|
||||||
Database: "zitadel",
|
password: "",
|
||||||
User: database.User{
|
|
||||||
Username: "zitadel-user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: sql.ErrConnDone,
|
targetErr: sql.ErrConnDone,
|
||||||
},
|
},
|
||||||
@ -38,12 +33,8 @@ func Test_verifyUser(t *testing.T) {
|
|||||||
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", false, "zitadel-user"),
|
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", false, "zitadel-user"),
|
||||||
expectExec("CREATE USER $1 WITH PASSWORD $2", sql.ErrTxDone, "zitadel-user", nil),
|
expectExec("CREATE USER $1 WITH PASSWORD $2", sql.ErrTxDone, "zitadel-user", nil),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
username: "zitadel-user",
|
||||||
Database: "zitadel",
|
password: "",
|
||||||
User: database.User{
|
|
||||||
Username: "zitadel-user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: sql.ErrTxDone,
|
targetErr: sql.ErrTxDone,
|
||||||
},
|
},
|
||||||
@ -54,12 +45,8 @@ func Test_verifyUser(t *testing.T) {
|
|||||||
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", false, "zitadel-user"),
|
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", false, "zitadel-user"),
|
||||||
expectExec("CREATE USER $1 WITH PASSWORD $2", nil, "zitadel-user", nil),
|
expectExec("CREATE USER $1 WITH PASSWORD $2", nil, "zitadel-user", nil),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
username: "zitadel-user",
|
||||||
Database: "zitadel",
|
password: "",
|
||||||
User: database.User{
|
|
||||||
Username: "zitadel-user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: nil,
|
targetErr: nil,
|
||||||
},
|
},
|
||||||
@ -70,13 +57,8 @@ func Test_verifyUser(t *testing.T) {
|
|||||||
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", false, "zitadel-user"),
|
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", false, "zitadel-user"),
|
||||||
expectExec("CREATE USER $1 WITH PASSWORD $2", nil, "zitadel-user", "password"),
|
expectExec("CREATE USER $1 WITH PASSWORD $2", nil, "zitadel-user", "password"),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
username: "zitadel-user",
|
||||||
Database: "zitadel",
|
password: "password",
|
||||||
User: database.User{
|
|
||||||
Username: "zitadel-user",
|
|
||||||
Password: "password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: nil,
|
targetErr: nil,
|
||||||
},
|
},
|
||||||
@ -86,20 +68,16 @@ func Test_verifyUser(t *testing.T) {
|
|||||||
db: prepareDB(t,
|
db: prepareDB(t,
|
||||||
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", true, "zitadel-user"),
|
expectExists("SELECT EXISTS(SELECT username FROM [show roles] WHERE username = $1)", true, "zitadel-user"),
|
||||||
),
|
),
|
||||||
config: database.Config{
|
username: "zitadel-user",
|
||||||
Database: "zitadel",
|
password: "",
|
||||||
User: database.User{
|
|
||||||
Username: "zitadel-user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
targetErr: nil,
|
targetErr: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if err := verifyUser(tt.args.config)(tt.args.db.db); !errors.Is(err, tt.targetErr) {
|
if err := VerifyUser(tt.args.username, tt.args.password)(tt.args.db.db); !errors.Is(err, tt.targetErr) {
|
||||||
t.Errorf("verifyGrant() error = %v, want: %v", err, tt.targetErr)
|
t.Errorf("VerifyGrant() error = %v, want: %v", err, tt.targetErr)
|
||||||
}
|
}
|
||||||
if err := tt.args.db.mock.ExpectationsWereMet(); err != nil {
|
if err := tt.args.db.mock.ExpectationsWereMet(); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
@ -12,16 +12,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
eventstoreSchema = "eventstore"
|
eventstoreSchema = "eventstore"
|
||||||
eventsTable = "events"
|
eventsTable = "events"
|
||||||
projectionsSchema = "projections"
|
uniqueConstraintsTable = "unique_constraints"
|
||||||
systemSchema = "system"
|
projectionsSchema = "projections"
|
||||||
encryptionKeysTable = "encryption_key"
|
systemSchema = "system"
|
||||||
|
encryptionKeysTable = "encryption_keys"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
searchTable = "SELECT table_name FROM [SHOW TABLES] WHERE table_name = $1"
|
searchSchema = "SELECT schema_name FROM [SHOW SCHEMAS] WHERE schema_name = $1"
|
||||||
searchSchema = "SELECT schema_name FROM [SHOW SCHEMAS] WHERE schema_name = $1"
|
searchTable = "SELECT table_name FROM [SHOW TABLES] WHERE table_name = $1"
|
||||||
|
searchSystemSequence = "SELECT sequence_name FROM [SHOW SEQUENCES] WHERE sequence_name = 'system_seq'"
|
||||||
|
|
||||||
//go:embed sql/04_eventstore.sql
|
//go:embed sql/04_eventstore.sql
|
||||||
createEventstoreStmt string
|
createEventstoreStmt string
|
||||||
//go:embed sql/05_projections.sql
|
//go:embed sql/05_projections.sql
|
||||||
@ -34,6 +37,10 @@ var (
|
|||||||
enableHashShardedIdx string
|
enableHashShardedIdx string
|
||||||
//go:embed sql/09_events_table.sql
|
//go:embed sql/09_events_table.sql
|
||||||
createEventsStmt string
|
createEventsStmt string
|
||||||
|
//go:embed sql/10_system_sequence.sql
|
||||||
|
createSystemSequenceStmt string
|
||||||
|
//go:embed sql/11_unique_constraints_table.sql
|
||||||
|
createUniqueConstraints string
|
||||||
)
|
)
|
||||||
|
|
||||||
func newZitadel() *cobra.Command {
|
func newZitadel() *cobra.Command {
|
||||||
@ -55,13 +62,7 @@ Prereqesits:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyZitadel(config database.Config) error {
|
func VerifyZitadel(db *sql.DB) error {
|
||||||
logging.WithFields("database", config.Database).Info("verify database")
|
|
||||||
db, err := database.Connect(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := verify(db, exists(searchSchema, systemSchema), exec(createSystemStmt)); err != nil {
|
if err := verify(db, exists(searchSchema, systemSchema), exec(createSystemStmt)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -82,6 +83,26 @@ func verifyZitadel(config database.Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := verify(db, exists(searchSystemSequence), exec(createSystemSequenceStmt)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := verify(db, exists(searchTable, uniqueConstraintsTable), exec(createUniqueConstraints)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyZitadel(config database.Config) error {
|
||||||
|
logging.WithFields("database", config.Database).Info("verify database")
|
||||||
|
db, err := database.Connect(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := VerifyZitadel(db); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return db.Close()
|
return db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ const (
|
|||||||
type CtxData struct {
|
type CtxData struct {
|
||||||
UserID string
|
UserID string
|
||||||
OrgID string
|
OrgID string
|
||||||
|
TenantID string //TODO: Set Tenant ID on some context
|
||||||
ProjectID string
|
ProjectID string
|
||||||
AgentID string
|
AgentID string
|
||||||
PreferredLanguage string
|
PreferredLanguage string
|
||||||
|
@ -2,11 +2,11 @@ package authz
|
|||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
func NewMockContext(orgID, userID string) context.Context {
|
func NewMockContext(tenantID, orgID, userID string) context.Context {
|
||||||
return context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID})
|
return context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID, TenantID: tenantID})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMockContextWithPermissions(orgID, userID string, permissions []string) context.Context {
|
func NewMockContextWithPermissions(tenantID, orgID, userID string, permissions []string) context.Context {
|
||||||
ctx := context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID})
|
ctx := context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID, TenantID: tenantID})
|
||||||
return context.WithValue(ctx, requestPermissionsKey, permissions)
|
return context.WithValue(ctx, requestPermissionsKey, permissions)
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ func (c *Commands) addOrgDomain(ctx context.Context, orgAgg *eventstore.Aggregat
|
|||||||
for _, userID := range claimedUserIDs {
|
for _, userID := range claimedUserIDs {
|
||||||
userEvents, _, err := c.userDomainClaimed(ctx, userID)
|
userEvents, _, err := c.userDomainClaimed(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("COMMAND-nn8Jf", "userid", userID).WithError(err).Warn("could not claim user")
|
logging.WithFields("userid", userID).WithError(err).Warn("could not claim user")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
events = append(events, userEvents...)
|
events = append(events, userEvents...)
|
||||||
|
@ -47,7 +47,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
},
|
},
|
||||||
@ -90,7 +90,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
ProjectID: "project1",
|
ProjectID: "project1",
|
||||||
@ -138,7 +138,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
ProjectID: "project1",
|
ProjectID: "project1",
|
||||||
@ -180,7 +180,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
ProjectID: "project1",
|
ProjectID: "project1",
|
||||||
@ -223,7 +223,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
ProjectID: "project1",
|
ProjectID: "project1",
|
||||||
@ -283,7 +283,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
ProjectID: "project1",
|
ProjectID: "project1",
|
||||||
@ -348,7 +348,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
|||||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "usergrant1"),
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "usergrant1"),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
ProjectID: "project1",
|
ProjectID: "project1",
|
||||||
@ -429,7 +429,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) {
|
|||||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "usergrant1"),
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "usergrant1"),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
ProjectID: "project1",
|
ProjectID: "project1",
|
||||||
@ -500,7 +500,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
},
|
},
|
||||||
@ -549,7 +549,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -573,7 +573,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -605,7 +605,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -662,7 +662,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -722,7 +722,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -776,7 +776,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -831,7 +831,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -903,7 +903,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("org", "user", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "org", "user", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -983,7 +983,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -1079,7 +1079,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrant: &domain.UserGrant{
|
userGrant: &domain.UserGrant{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "usergrant1",
|
AggregateID: "usergrant1",
|
||||||
@ -1183,7 +1183,7 @@ func TestCommandSide_DeactivateUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1215,7 +1215,7 @@ func TestCommandSide_DeactivateUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1303,7 +1303,7 @@ func TestCommandSide_DeactivateUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1391,7 +1391,7 @@ func TestCommandSide_ReactivateUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1423,7 +1423,7 @@ func TestCommandSide_ReactivateUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1515,7 +1515,7 @@ func TestCommandSide_ReactivateUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1588,7 +1588,7 @@ func TestCommandSide_RemoveUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1620,7 +1620,7 @@ func TestCommandSide_RemoveUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1687,7 +1687,7 @@ func TestCommandSide_RemoveUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1727,7 +1727,7 @@ func TestCommandSide_RemoveUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantID: "usergrant1",
|
userGrantID: "usergrant1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1800,7 +1800,7 @@ func TestCommandSide_BulkRemoveUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantIDs: []string{"usergrant1", "usergrant2"},
|
userGrantIDs: []string{"usergrant1", "usergrant2"},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1832,7 +1832,7 @@ func TestCommandSide_BulkRemoveUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantIDs: []string{"usergrant1", "usergrant2"},
|
userGrantIDs: []string{"usergrant1", "usergrant2"},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1913,7 +1913,7 @@ func TestCommandSide_BulkRemoveUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantIDs: []string{"usergrant1", "usergrant2"},
|
userGrantIDs: []string{"usergrant1", "usergrant2"},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@ -1967,7 +1967,7 @@ func TestCommandSide_BulkRemoveUserGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: authz.NewMockContextWithPermissions("", "", []string{domain.RoleProjectOwner}),
|
ctx: authz.NewMockContextWithPermissions("", "", "", []string{domain.RoleProjectOwner}),
|
||||||
userGrantIDs: []string{"usergrant1", "usergrant2"},
|
userGrantIDs: []string{"usergrant1", "usergrant2"},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
|
@ -21,6 +21,7 @@ func NewAggregate(
|
|||||||
ID: id,
|
ID: id,
|
||||||
Type: typ,
|
Type: typ,
|
||||||
ResourceOwner: authz.GetCtxData(ctx).OrgID,
|
ResourceOwner: authz.GetCtxData(ctx).OrgID,
|
||||||
|
Tenant: authz.GetCtxData(ctx).TenantID,
|
||||||
Version: version,
|
Version: version,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ func AggregateFromWriteModel(
|
|||||||
ID: wm.AggregateID,
|
ID: wm.AggregateID,
|
||||||
Type: typ,
|
Type: typ,
|
||||||
ResourceOwner: wm.ResourceOwner,
|
ResourceOwner: wm.ResourceOwner,
|
||||||
|
Tenant: wm.Tenant,
|
||||||
Version: version,
|
Version: version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,6 +63,8 @@ type Aggregate struct {
|
|||||||
Type AggregateType `json:"-"`
|
Type AggregateType `json:"-"`
|
||||||
//ResourceOwner is the org this aggregates belongs to
|
//ResourceOwner is the org this aggregates belongs to
|
||||||
ResourceOwner string `json:"-"`
|
ResourceOwner string `json:"-"`
|
||||||
|
//Tenant is the system this aggregate belongs to
|
||||||
|
Tenant string `json:"-"`
|
||||||
//Version is the semver this aggregate represents
|
//Version is the semver this aggregate represents
|
||||||
Version Version `json:"-"`
|
Version Version `json:"-"`
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ func BaseEventFromRepo(event *repository.Event) *BaseEvent {
|
|||||||
ID: event.AggregateID,
|
ID: event.AggregateID,
|
||||||
Type: AggregateType(event.AggregateType),
|
Type: AggregateType(event.AggregateType),
|
||||||
ResourceOwner: event.ResourceOwner.String,
|
ResourceOwner: event.ResourceOwner.String,
|
||||||
|
Tenant: event.Tenant.String,
|
||||||
Version: Version(event.Version),
|
Version: Version(event.Version),
|
||||||
},
|
},
|
||||||
EventType: EventType(event.Type),
|
EventType: EventType(event.Type),
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
)
|
)
|
||||||
@ -40,7 +41,7 @@ func (es *Eventstore) Health(ctx context.Context) error {
|
|||||||
//Push pushes the events in a single transaction
|
//Push pushes the events in a single transaction
|
||||||
// an event needs at least an aggregate
|
// an event needs at least an aggregate
|
||||||
func (es *Eventstore) Push(ctx context.Context, cmds ...Command) ([]Event, error) {
|
func (es *Eventstore) Push(ctx context.Context, cmds ...Command) ([]Event, error) {
|
||||||
events, constraints, err := commandsToRepository(cmds)
|
events, constraints, err := commandsToRepository(authz.GetCtxData(ctx).TenantID, cmds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -58,7 +59,7 @@ func (es *Eventstore) Push(ctx context.Context, cmds ...Command) ([]Event, error
|
|||||||
return eventReaders, nil
|
return eventReaders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandsToRepository(cmds []Command) (events []*repository.Event, constraints []*repository.UniqueConstraint, err error) {
|
func commandsToRepository(tenantID string, cmds []Command) (events []*repository.Event, constraints []*repository.UniqueConstraint, err error) {
|
||||||
events = make([]*repository.Event, len(cmds))
|
events = make([]*repository.Event, len(cmds))
|
||||||
for i, cmd := range cmds {
|
for i, cmd := range cmds {
|
||||||
data, err := EventData(cmd)
|
data, err := EventData(cmd)
|
||||||
@ -81,6 +82,7 @@ func commandsToRepository(cmds []Command) (events []*repository.Event, constrain
|
|||||||
AggregateID: cmd.Aggregate().ID,
|
AggregateID: cmd.Aggregate().ID,
|
||||||
AggregateType: repository.AggregateType(cmd.Aggregate().Type),
|
AggregateType: repository.AggregateType(cmd.Aggregate().Type),
|
||||||
ResourceOwner: sql.NullString{String: cmd.Aggregate().ResourceOwner, Valid: cmd.Aggregate().ResourceOwner != ""},
|
ResourceOwner: sql.NullString{String: cmd.Aggregate().ResourceOwner, Valid: cmd.Aggregate().ResourceOwner != ""},
|
||||||
|
Tenant: sql.NullString{String: tenantID, Valid: tenantID != ""},
|
||||||
EditorService: cmd.EditorService(),
|
EditorService: cmd.EditorService(),
|
||||||
EditorUser: cmd.EditorUser(),
|
EditorUser: cmd.EditorUser(),
|
||||||
Type: repository.EventType(cmd.Type()),
|
Type: repository.EventType(cmd.Type()),
|
||||||
@ -111,7 +113,7 @@ func uniqueConstraintsToRepository(constraints []*EventUniqueConstraint) (unique
|
|||||||
//Filter filters the stored events based on the searchQuery
|
//Filter filters the stored events based on the searchQuery
|
||||||
// and maps the events to the defined event structs
|
// and maps the events to the defined event structs
|
||||||
func (es *Eventstore) Filter(ctx context.Context, queryFactory *SearchQueryBuilder) ([]Event, error) {
|
func (es *Eventstore) Filter(ctx context.Context, queryFactory *SearchQueryBuilder) ([]Event, error) {
|
||||||
query, err := queryFactory.build()
|
query, err := queryFactory.build(authz.GetCtxData(ctx).TenantID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -168,7 +170,7 @@ func (es *Eventstore) FilterToReducer(ctx context.Context, searchQuery *SearchQu
|
|||||||
|
|
||||||
//LatestSequence filters the latest sequence for the given search query
|
//LatestSequence filters the latest sequence for the given search query
|
||||||
func (es *Eventstore) LatestSequence(ctx context.Context, queryFactory *SearchQueryBuilder) (uint64, error) {
|
func (es *Eventstore) LatestSequence(ctx context.Context, queryFactory *SearchQueryBuilder) (uint64, error) {
|
||||||
query, err := queryFactory.build()
|
query, err := queryFactory.build(authz.GetCtxData(ctx).TenantID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,8 @@ func newTestEvent(id, description string, data func() interface{}, checkPrevious
|
|||||||
data: data,
|
data: data,
|
||||||
shouldCheckPrevious: checkPrevious,
|
shouldCheckPrevious: checkPrevious,
|
||||||
BaseEvent: *NewBaseEventForPush(
|
BaseEvent: *NewBaseEventForPush(
|
||||||
service.WithService(authz.NewMockContext("resourceOwner", "editorUser"), "editorService"),
|
service.WithService(authz.NewMockContext("tenant", "resourceOwner", "editorUser"), "editorService"),
|
||||||
NewAggregate(authz.NewMockContext("caos", "adlerhurst"), id, "test.aggregate", "v1"),
|
NewAggregate(authz.NewMockContext("zitadel", "caos", "adlerhurst"), id, "test.aggregate", "v1"),
|
||||||
"test.event",
|
"test.event",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -344,7 +344,8 @@ func Test_eventData(t *testing.T) {
|
|||||||
|
|
||||||
func TestEventstore_aggregatesToEvents(t *testing.T) {
|
func TestEventstore_aggregatesToEvents(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
events []Command
|
tenantID string
|
||||||
|
events []Command
|
||||||
}
|
}
|
||||||
type res struct {
|
type res struct {
|
||||||
wantErr bool
|
wantErr bool
|
||||||
@ -358,6 +359,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "one aggregate one event",
|
name: "one aggregate one event",
|
||||||
args: args{
|
args: args{
|
||||||
|
tenantID: "tenant",
|
||||||
events: []Command{
|
events: []Command{
|
||||||
newTestEvent(
|
newTestEvent(
|
||||||
"1",
|
"1",
|
||||||
@ -378,6 +380,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "tenant", Valid: true},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -387,6 +390,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "one aggregate multiple events",
|
name: "one aggregate multiple events",
|
||||||
args: args{
|
args: args{
|
||||||
|
tenantID: "tenant",
|
||||||
events: []Command{
|
events: []Command{
|
||||||
newTestEvent(
|
newTestEvent(
|
||||||
"1",
|
"1",
|
||||||
@ -414,6 +418,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "tenant", Valid: true},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -424,6 +429,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "tenant", Valid: true},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -433,6 +439,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid data",
|
name: "invalid data",
|
||||||
args: args{
|
args: args{
|
||||||
|
tenantID: "tenant",
|
||||||
events: []Command{
|
events: []Command{
|
||||||
newTestEvent(
|
newTestEvent(
|
||||||
"1",
|
"1",
|
||||||
@ -453,9 +460,9 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
events: []Command{
|
events: []Command{
|
||||||
&testEvent{
|
&testEvent{
|
||||||
BaseEvent: *NewBaseEventForPush(
|
BaseEvent: *NewBaseEventForPush(
|
||||||
service.WithService(authz.NewMockContext("resourceOwner", "editorUser"), "editorService"),
|
service.WithService(authz.NewMockContext("tenant", "resourceOwner", "editorUser"), "editorService"),
|
||||||
NewAggregate(
|
NewAggregate(
|
||||||
authz.NewMockContext("caos", "adlerhurst"),
|
authz.NewMockContext("zitadel", "caos", "adlerhurst"),
|
||||||
"",
|
"",
|
||||||
"test.aggregate",
|
"test.aggregate",
|
||||||
"v1",
|
"v1",
|
||||||
@ -478,9 +485,9 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
events: []Command{
|
events: []Command{
|
||||||
&testEvent{
|
&testEvent{
|
||||||
BaseEvent: *NewBaseEventForPush(
|
BaseEvent: *NewBaseEventForPush(
|
||||||
service.WithService(authz.NewMockContext("resourceOwner", "editorUser"), "editorService"),
|
service.WithService(authz.NewMockContext("tenant", "resourceOwner", "editorUser"), "editorService"),
|
||||||
NewAggregate(
|
NewAggregate(
|
||||||
authz.NewMockContext("caos", "adlerhurst"),
|
authz.NewMockContext("zitadel", "caos", "adlerhurst"),
|
||||||
"id",
|
"id",
|
||||||
"",
|
"",
|
||||||
"v1",
|
"v1",
|
||||||
@ -503,9 +510,9 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
events: []Command{
|
events: []Command{
|
||||||
&testEvent{
|
&testEvent{
|
||||||
BaseEvent: *NewBaseEventForPush(
|
BaseEvent: *NewBaseEventForPush(
|
||||||
service.WithService(authz.NewMockContext("resourceOwner", "editorUser"), "editorService"),
|
service.WithService(authz.NewMockContext("tenant", "resourceOwner", "editorUser"), "editorService"),
|
||||||
NewAggregate(
|
NewAggregate(
|
||||||
authz.NewMockContext("caos", "adlerhurst"),
|
authz.NewMockContext("zitadel", "caos", "adlerhurst"),
|
||||||
"id",
|
"id",
|
||||||
"test.aggregate",
|
"test.aggregate",
|
||||||
"",
|
"",
|
||||||
@ -528,9 +535,9 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
events: []Command{
|
events: []Command{
|
||||||
&testEvent{
|
&testEvent{
|
||||||
BaseEvent: *NewBaseEventForPush(
|
BaseEvent: *NewBaseEventForPush(
|
||||||
service.WithService(authz.NewMockContext("resourceOwner", "editorUser"), "editorService"),
|
service.WithService(authz.NewMockContext("tenant", "resourceOwner", "editorUser"), "editorService"),
|
||||||
NewAggregate(
|
NewAggregate(
|
||||||
authz.NewMockContext("caos", "adlerhurst"),
|
authz.NewMockContext("zitadel", "caos", "adlerhurst"),
|
||||||
"id",
|
"id",
|
||||||
"test.aggregate",
|
"test.aggregate",
|
||||||
"v1",
|
"v1",
|
||||||
@ -553,9 +560,9 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
events: []Command{
|
events: []Command{
|
||||||
&testEvent{
|
&testEvent{
|
||||||
BaseEvent: *NewBaseEventForPush(
|
BaseEvent: *NewBaseEventForPush(
|
||||||
service.WithService(authz.NewMockContext("", "editorUser"), "editorService"),
|
service.WithService(authz.NewMockContext("tenant", "", "editorUser"), "editorService"),
|
||||||
NewAggregate(
|
NewAggregate(
|
||||||
authz.NewMockContext("", "adlerhurst"),
|
authz.NewMockContext("zitadel", "", "adlerhurst"),
|
||||||
"id",
|
"id",
|
||||||
"test.aggregate",
|
"test.aggregate",
|
||||||
"v1",
|
"v1",
|
||||||
@ -578,6 +585,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "", Valid: false},
|
ResourceOwner: sql.NullString{String: "", Valid: false},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -622,6 +630,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -632,6 +641,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -644,6 +654,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -654,7 +665,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
events, _, err := commandsToRepository(tt.args.events)
|
events, _, err := commandsToRepository(tt.args.tenantID, tt.args.events)
|
||||||
if (err != nil) != tt.res.wantErr {
|
if (err != nil) != tt.res.wantErr {
|
||||||
t.Errorf("Eventstore.aggregatesToEvents() error = %v, wantErr %v", err, tt.res.wantErr)
|
t.Errorf("Eventstore.aggregatesToEvents() error = %v, wantErr %v", err, tt.res.wantErr)
|
||||||
return
|
return
|
||||||
@ -761,6 +772,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -804,6 +816,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -814,6 +827,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -868,6 +882,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -878,6 +893,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
@ -890,6 +906,7 @@ func TestEventstore_Push(t *testing.T) {
|
|||||||
EditorService: "editorService",
|
EditorService: "editorService",
|
||||||
EditorUser: "editorUser",
|
EditorUser: "editorUser",
|
||||||
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
ResourceOwner: sql.NullString{String: "caos", Valid: true},
|
||||||
|
Tenant: sql.NullString{String: "zitadel"},
|
||||||
Type: "test.event",
|
Type: "test.event",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
},
|
},
|
||||||
|
@ -3,7 +3,6 @@ package eventstore_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -18,7 +17,7 @@ import (
|
|||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
func NewUserAggregate(id string) *eventstore.Aggregate {
|
func NewUserAggregate(id string) *eventstore.Aggregate {
|
||||||
return eventstore.NewAggregate(
|
return eventstore.NewAggregate(
|
||||||
authz.NewMockContext("caos", "adlerhurst"),
|
authz.NewMockContext("zitadel", "caos", "adlerhurst"),
|
||||||
id,
|
id,
|
||||||
"test.user",
|
"test.user",
|
||||||
"v1",
|
"v1",
|
||||||
@ -308,7 +307,7 @@ func TestUserReadModel(t *testing.T) {
|
|||||||
|
|
||||||
events = append(events, nil)
|
events = append(events, nil)
|
||||||
|
|
||||||
fmt.Printf("%+v\n", events)
|
t.Logf("%+v\n", events)
|
||||||
|
|
||||||
users := UsersReadModel{}
|
users := UsersReadModel{}
|
||||||
|
|
||||||
@ -316,5 +315,5 @@ func TestUserReadModel(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error on filter to reducer: %v", err)
|
t.Errorf("unexpected error on filter to reducer: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("%+v", users)
|
t.Logf("%+v", users)
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,12 @@ const (
|
|||||||
func (h *StatementHandler) handleFailedStmt(tx *sql.Tx, stmt *handler.Statement, execErr error) (shouldContinue bool) {
|
func (h *StatementHandler) handleFailedStmt(tx *sql.Tx, stmt *handler.Statement, execErr error) (shouldContinue bool) {
|
||||||
failureCount, err := h.failureCount(tx, stmt.Sequence)
|
failureCount, err := h.failureCount(tx, stmt.Sequence)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("CRDB-WJaFk", "projection", h.ProjectionName, "seq", stmt.Sequence).WithError(err).Warn("unable to get failure count")
|
logging.WithFields("projection", h.ProjectionName, "seq", stmt.Sequence).WithError(err).Warn("unable to get failure count")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
failureCount += 1
|
failureCount += 1
|
||||||
err = h.setFailureCount(tx, stmt.Sequence, failureCount, execErr)
|
err = h.setFailureCount(tx, stmt.Sequence, failureCount, execErr)
|
||||||
logging.LogWithFields("CRDB-cI0dB", "projection", h.ProjectionName, "seq", stmt.Sequence).OnError(err).Warn("unable to update failure count")
|
logging.WithFields("projection", h.ProjectionName, "seq", stmt.Sequence).OnError(err).Warn("unable to update failure count")
|
||||||
|
|
||||||
return failureCount >= h.maxFailureCount
|
return failureCount >= h.maxFailureCount
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,7 @@ func (h *StatementHandler) executeStmts(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if stmt.PreviousSequence > 0 && stmt.PreviousSequence != sequences[stmt.AggregateType] {
|
if stmt.PreviousSequence > 0 && stmt.PreviousSequence != sequences[stmt.AggregateType] {
|
||||||
logging.LogWithFields("CRDB-jJBJn", "projection", h.ProjectionName, "aggregateType", stmt.AggregateType, "seq", stmt.Sequence, "prevSeq", stmt.PreviousSequence, "currentSeq", sequences[stmt.AggregateType]).Warn("sequences do not match")
|
logging.WithFields("projection", h.ProjectionName, "aggregateType", stmt.AggregateType, "seq", stmt.Sequence, "prevSeq", stmt.PreviousSequence, "currentSeq", sequences[stmt.AggregateType]).Warn("sequences do not match")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
err := h.executeStmt(tx, stmt)
|
err := h.executeStmt(tx, stmt)
|
||||||
|
@ -69,12 +69,12 @@ func NewProjectionHandler(config ProjectionHandlerConfig) *ProjectionHandler {
|
|||||||
if !h.shouldBulk.Stop() {
|
if !h.shouldBulk.Stop() {
|
||||||
<-h.shouldBulk.C
|
<-h.shouldBulk.C
|
||||||
}
|
}
|
||||||
logging.LogWithFields("HANDL-mC9Xx", "projection", h.ProjectionName).Info("starting handler without requeue")
|
logging.WithFields("projection", h.ProjectionName).Info("starting handler without requeue")
|
||||||
return h
|
return h
|
||||||
} else if config.RequeueEvery < 500*time.Millisecond {
|
} else if config.RequeueEvery < 500*time.Millisecond {
|
||||||
logging.LogWithFields("HANDL-IEFsG", "projection", h.ProjectionName).Fatal("requeue every must be greater 500ms or <= 0")
|
logging.WithFields("projection", h.ProjectionName).Fatal("requeue every must be greater 500ms or <= 0")
|
||||||
}
|
}
|
||||||
logging.LogWithFields("HANDL-fAC5O", "projection", h.ProjectionName).Info("starting handler")
|
logging.WithFields("projection", h.ProjectionName).Info("starting handler")
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ func (h *ProjectionHandler) Process(
|
|||||||
//handle panic
|
//handle panic
|
||||||
defer func() {
|
defer func() {
|
||||||
cause := recover()
|
cause := recover()
|
||||||
logging.LogWithFields("HANDL-utWkv", "projection", h.ProjectionName, "cause", cause, "stack", string(debug.Stack())).Error("projection handler paniced")
|
logging.WithFields("projection", h.ProjectionName, "cause", cause, "stack", string(debug.Stack())).Error("projection handler paniced")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
execBulk := h.prepareExecuteBulk(query, reduce, update)
|
execBulk := h.prepareExecuteBulk(query, reduce, update)
|
||||||
@ -121,7 +121,7 @@ func (h *ProjectionHandler) Process(
|
|||||||
return
|
return
|
||||||
case event := <-h.Handler.EventQueue:
|
case event := <-h.Handler.EventQueue:
|
||||||
if err := h.processEvent(ctx, event, reduce); err != nil {
|
if err := h.processEvent(ctx, event, reduce); err != nil {
|
||||||
logging.LogWithFields("HANDL-TUk5J", "projection", h.ProjectionName).WithError(err).Warn("process failed")
|
logging.WithFields("projection", h.ProjectionName).WithError(err).Warn("process failed")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
h.triggerShouldPush(0)
|
h.triggerShouldPush(0)
|
||||||
@ -139,7 +139,7 @@ func (h *ProjectionHandler) Process(
|
|||||||
return
|
return
|
||||||
case event := <-h.Handler.EventQueue:
|
case event := <-h.Handler.EventQueue:
|
||||||
if err := h.processEvent(ctx, event, reduce); err != nil {
|
if err := h.processEvent(ctx, event, reduce); err != nil {
|
||||||
logging.LogWithFields("HANDL-horKq", "projection", h.ProjectionName).WithError(err).Warn("process failed")
|
logging.WithFields("projection", h.ProjectionName).WithError(err).Warn("process failed")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
h.triggerShouldPush(0)
|
h.triggerShouldPush(0)
|
||||||
@ -161,7 +161,7 @@ func (h *ProjectionHandler) processEvent(
|
|||||||
) error {
|
) error {
|
||||||
stmt, err := reduce(event)
|
stmt, err := reduce(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("EVENT-PTr4j").WithError(err).Warn("unable to process event")
|
logging.New().WithError(err).Warn("unable to process event")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,16 +185,16 @@ func (h *ProjectionHandler) bulk(
|
|||||||
errs := lock(ctx, h.requeueAfter)
|
errs := lock(ctx, h.requeueAfter)
|
||||||
//wait until projection is locked
|
//wait until projection is locked
|
||||||
if err, ok := <-errs; err != nil || !ok {
|
if err, ok := <-errs; err != nil || !ok {
|
||||||
logging.LogWithFields("HANDL-XDJ4i", "projection", h.ProjectionName).OnError(err).Warn("initial lock failed")
|
logging.WithFields("projection", h.ProjectionName).OnError(err).Warn("initial lock failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go h.cancelOnErr(ctx, errs, cancel)
|
go h.cancelOnErr(ctx, errs, cancel)
|
||||||
|
|
||||||
execErr := executeBulk(ctx)
|
execErr := executeBulk(ctx)
|
||||||
logging.LogWithFields("EVENT-gwiu4", "projection", h.ProjectionName).OnError(execErr).Warn("unable to execute")
|
logging.WithFields("projection", h.ProjectionName).OnError(execErr).Warn("unable to execute")
|
||||||
|
|
||||||
unlockErr := unlock()
|
unlockErr := unlock()
|
||||||
logging.LogWithFields("EVENT-boPv1", "projection", h.ProjectionName).OnError(unlockErr).Warn("unable to unlock")
|
logging.WithFields("projection", h.ProjectionName).OnError(unlockErr).Warn("unable to unlock")
|
||||||
|
|
||||||
if execErr != nil {
|
if execErr != nil {
|
||||||
return execErr
|
return execErr
|
||||||
@ -208,7 +208,7 @@ func (h *ProjectionHandler) cancelOnErr(ctx context.Context, errs <-chan error,
|
|||||||
select {
|
select {
|
||||||
case err := <-errs:
|
case err := <-errs:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("HANDL-cVop2", "projection", h.ProjectionName).WithError(err).Warn("bulk canceled")
|
logging.WithFields("projection", h.ProjectionName).WithError(err).Warn("bulk canceled")
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -235,7 +235,7 @@ func (h *ProjectionHandler) prepareExecuteBulk(
|
|||||||
default:
|
default:
|
||||||
hasLimitExeeded, err := h.fetchBulkStmts(ctx, query, reduce)
|
hasLimitExeeded, err := h.fetchBulkStmts(ctx, query, reduce)
|
||||||
if err != nil || len(h.stmts) == 0 {
|
if err != nil || len(h.stmts) == 0 {
|
||||||
logging.LogWithFields("HANDL-CzQvn", "projection", h.ProjectionName).OnError(err).Warn("unable to fetch stmts")
|
logging.WithFields("projection", h.ProjectionName).OnError(err).Warn("unable to fetch stmts")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,19 +258,19 @@ func (h *ProjectionHandler) fetchBulkStmts(
|
|||||||
) (limitExeeded bool, err error) {
|
) (limitExeeded bool, err error) {
|
||||||
eventQuery, eventsLimit, err := query()
|
eventQuery, eventsLimit, err := query()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("HANDL-x6qvs", "projection", h.ProjectionName).WithError(err).Warn("unable to create event query")
|
logging.WithFields("projection", h.ProjectionName).WithError(err).Warn("unable to create event query")
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
events, err := h.Eventstore.Filter(ctx, eventQuery)
|
events, err := h.Eventstore.Filter(ctx, eventQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("HANDL-X8vlo", "projection", h.ProjectionName).WithError(err).Info("Unable to bulk fetch events")
|
logging.WithFields("projection", h.ProjectionName).WithError(err).Info("Unable to bulk fetch events")
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
if err = h.processEvent(ctx, event, reduce); err != nil {
|
if err = h.processEvent(ctx, event, reduce); err != nil {
|
||||||
logging.LogWithFields("HANDL-PaKlz", "projection", h.ProjectionName, "seq", event.Sequence()).WithError(err).Warn("unable to process event in bulk")
|
logging.WithFields("projection", h.ProjectionName, "seq", event.Sequence()).WithError(err).Warn("unable to process event in bulk")
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,5 +313,5 @@ func (h *ProjectionHandler) shutdown() {
|
|||||||
if !h.shouldPush.Stop() {
|
if !h.shouldPush.Stop() {
|
||||||
<-h.shouldPush.C
|
<-h.shouldPush.C
|
||||||
}
|
}
|
||||||
logging.Log("EVENT-XG5Og").Info("stop processing")
|
logging.New().Info("stop processing")
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ func NewCol(name string, value interface{}) Column {
|
|||||||
func NewJSONCol(name string, value interface{}) Column {
|
func NewJSONCol(name string, value interface{}) Column {
|
||||||
marshalled, err := json.Marshal(value)
|
marshalled, err := json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("HANDL-oFvsl", "column", name).WithError(err).Panic("unable to marshal column")
|
logging.WithFields("column", name).WithError(err).Panic("unable to marshal column")
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewCol(name, marshalled)
|
return NewCol(name, marshalled)
|
||||||
|
@ -2,33 +2,34 @@ package eventstore_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/cockroachdb/cockroach-go/v2/testserver"
|
"github.com/cockroachdb/cockroach-go/v2/testserver"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/cmd/admin/initialise"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
migrationsPath = os.ExpandEnv("${GOPATH}/src/github.com/caos/zitadel/migrations/cockroach")
|
|
||||||
testCRDBClient *sql.DB
|
testCRDBClient *sql.DB
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
ts, err := testserver.NewTestServer()
|
ts, err := testserver.NewTestServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("REPOS-RvjLG", "error", err).Fatal("unable to start db")
|
logging.WithFields("error", err).Fatal("unable to start db")
|
||||||
}
|
}
|
||||||
|
|
||||||
testCRDBClient, err = sql.Open("postgres", ts.PGURL().String())
|
testCRDBClient, err = sql.Open("postgres", ts.PGURL().String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("REPOS-CF6dQ", "error", err).Fatal("unable to connect to db")
|
logging.WithFields("error", err).Fatal("unable to connect to db")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logging.WithFields("error", err).Fatal("unable to connect to db")
|
||||||
|
}
|
||||||
|
if err = testCRDBClient.Ping(); err != nil {
|
||||||
|
logging.WithFields("error", err).Fatal("unable to ping db")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -36,108 +37,21 @@ func TestMain(m *testing.M) {
|
|||||||
ts.Stop()
|
ts.Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err = executeMigrations(); err != nil {
|
if err = initDB(testCRDBClient); err != nil {
|
||||||
logging.LogWithFields("REPOS-jehDD", "error", err).Fatal("migrations failed")
|
logging.WithFields("error", err).Fatal("migrations failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeMigrations() error {
|
func initDB(db *sql.DB) error {
|
||||||
files, err := migrationFilePaths()
|
username := "zitadel"
|
||||||
|
database := "zitadel"
|
||||||
|
err := initialise.Initialise(db, initialise.VerifyUser(username, ""),
|
||||||
|
initialise.VerifyDatabase(database),
|
||||||
|
initialise.VerifyGrant(database, username))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sort.Sort(files)
|
return initialise.VerifyZitadel(db)
|
||||||
if err = setPasswordNULL(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = createFlywayHistory(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
migrationData, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
migration := os.ExpandEnv(string(migrationData))
|
|
||||||
if _, err = testCRDBClient.Exec(migration); err != nil {
|
|
||||||
return fmt.Errorf("exec file: %v || err: %w", file, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setPasswordNULL() error {
|
|
||||||
passwordNames := []string{
|
|
||||||
"eventstorepassword",
|
|
||||||
"managementpassword",
|
|
||||||
"adminapipassword",
|
|
||||||
"authpassword",
|
|
||||||
"notificationpassword",
|
|
||||||
"authzpassword",
|
|
||||||
"queriespassword",
|
|
||||||
}
|
|
||||||
for _, name := range passwordNames {
|
|
||||||
if err := os.Setenv(name, "NULL"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createFlywayHistory() error {
|
|
||||||
_, err := testCRDBClient.Exec("CREATE TABLE defaultdb.flyway_schema_history(id TEXT, PRIMARY KEY(id));")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type migrationPaths []string
|
|
||||||
|
|
||||||
type version struct {
|
|
||||||
major int
|
|
||||||
minor int
|
|
||||||
}
|
|
||||||
|
|
||||||
func versionFromPath(s string) version {
|
|
||||||
v := s[strings.Index(s, "/V")+2 : strings.Index(s, "__")]
|
|
||||||
splitted := strings.Split(v, ".")
|
|
||||||
res := version{}
|
|
||||||
var err error
|
|
||||||
if len(splitted) >= 1 {
|
|
||||||
res.major, err = strconv.Atoi(splitted[0])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(splitted) >= 2 {
|
|
||||||
res.minor, err = strconv.Atoi(splitted[1])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a migrationPaths) Len() int { return len(a) }
|
|
||||||
func (a migrationPaths) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a migrationPaths) Less(i, j int) bool {
|
|
||||||
versionI := versionFromPath(a[i])
|
|
||||||
versionJ := versionFromPath(a[j])
|
|
||||||
|
|
||||||
return versionI.major < versionJ.major ||
|
|
||||||
(versionI.major == versionJ.major && versionI.minor < versionJ.minor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrationFilePaths() (migrationPaths, error) {
|
|
||||||
files := make(migrationPaths, 0)
|
|
||||||
err := filepath.Walk(migrationsPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil || info.IsDir() || !strings.HasSuffix(info.Name(), ".sql") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
files = append(files, path)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return files, err
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ type ReadModel struct {
|
|||||||
ChangeDate time.Time `json:"-"`
|
ChangeDate time.Time `json:"-"`
|
||||||
Events []Event `json:"-"`
|
Events []Event `json:"-"`
|
||||||
ResourceOwner string `json:"-"`
|
ResourceOwner string `json:"-"`
|
||||||
|
Tenant string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//AppendEvents adds all the events to the read model.
|
//AppendEvents adds all the events to the read model.
|
||||||
@ -34,6 +35,9 @@ func (rm *ReadModel) Reduce() error {
|
|||||||
if rm.ResourceOwner == "" {
|
if rm.ResourceOwner == "" {
|
||||||
rm.ResourceOwner = rm.Events[0].Aggregate().ResourceOwner
|
rm.ResourceOwner = rm.Events[0].Aggregate().ResourceOwner
|
||||||
}
|
}
|
||||||
|
if rm.Tenant == "" {
|
||||||
|
rm.Tenant = rm.Events[0].Aggregate().Tenant
|
||||||
|
}
|
||||||
|
|
||||||
if rm.CreationDate.IsZero() {
|
if rm.CreationDate.IsZero() {
|
||||||
rm.CreationDate = rm.Events[0].CreationDate()
|
rm.CreationDate = rm.Events[0].CreationDate()
|
||||||
|
@ -56,6 +56,9 @@ type Event struct {
|
|||||||
// an aggregate can only be managed by one organisation
|
// an aggregate can only be managed by one organisation
|
||||||
// use the ID of the org
|
// use the ID of the org
|
||||||
ResourceOwner sql.NullString
|
ResourceOwner sql.NullString
|
||||||
|
//Tenant is the system where this event belongs to
|
||||||
|
// use the ID of the tenant
|
||||||
|
Tenant sql.NullString
|
||||||
}
|
}
|
||||||
|
|
||||||
//EventType is the description of the change
|
//EventType is the description of the change
|
||||||
|
@ -66,6 +66,8 @@ const (
|
|||||||
FieldSequence
|
FieldSequence
|
||||||
//FieldResourceOwner represents the resource owner field
|
//FieldResourceOwner represents the resource owner field
|
||||||
FieldResourceOwner
|
FieldResourceOwner
|
||||||
|
//FieldTenant represents the tenant field
|
||||||
|
FieldTenant
|
||||||
//FieldEditorService represents the editor service field
|
//FieldEditorService represents the editor service field
|
||||||
FieldEditorService
|
FieldEditorService
|
||||||
//FieldEditorUser represents the editor user field
|
//FieldEditorUser represents the editor user field
|
||||||
|
@ -9,10 +9,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
|
||||||
"github.com/cockroachdb/cockroach-go/v2/crdb"
|
"github.com/cockroachdb/cockroach-go/v2/crdb"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -29,6 +30,7 @@ const (
|
|||||||
" SELECT MAX(event_sequence) seq, 1 join_me" +
|
" SELECT MAX(event_sequence) seq, 1 join_me" +
|
||||||
" FROM eventstore.events" +
|
" FROM eventstore.events" +
|
||||||
" WHERE aggregate_type = $2" +
|
" WHERE aggregate_type = $2" +
|
||||||
|
" AND (CASE WHEN $9::STRING IS NULL THEN tenant is null else tenant = $9::STRING END)" +
|
||||||
") AS agg_type " +
|
") AS agg_type " +
|
||||||
// combined with
|
// combined with
|
||||||
"LEFT JOIN " +
|
"LEFT JOIN " +
|
||||||
@ -37,6 +39,7 @@ const (
|
|||||||
" SELECT event_sequence seq, resource_owner ro, 1 join_me" +
|
" SELECT event_sequence seq, resource_owner ro, 1 join_me" +
|
||||||
" FROM eventstore.events" +
|
" FROM eventstore.events" +
|
||||||
" WHERE aggregate_type = $2 AND aggregate_id = $3" +
|
" WHERE aggregate_type = $2 AND aggregate_id = $3" +
|
||||||
|
" AND (CASE WHEN $9::STRING IS NULL THEN tenant is null else tenant = $9::STRING END)" +
|
||||||
" ORDER BY event_sequence DESC" +
|
" ORDER BY event_sequence DESC" +
|
||||||
" LIMIT 1" +
|
" LIMIT 1" +
|
||||||
") AS agg USING(join_me)" +
|
") AS agg USING(join_me)" +
|
||||||
@ -51,6 +54,8 @@ const (
|
|||||||
" editor_user," +
|
" editor_user," +
|
||||||
" editor_service," +
|
" editor_service," +
|
||||||
" resource_owner," +
|
" resource_owner," +
|
||||||
|
" tenant," +
|
||||||
|
" event_sequence," +
|
||||||
" previous_aggregate_sequence," +
|
" previous_aggregate_sequence," +
|
||||||
" previous_aggregate_type_sequence" +
|
" previous_aggregate_type_sequence" +
|
||||||
") " +
|
") " +
|
||||||
@ -64,11 +69,13 @@ const (
|
|||||||
" $5::JSONB AS event_data," +
|
" $5::JSONB AS event_data," +
|
||||||
" $6::VARCHAR AS editor_user," +
|
" $6::VARCHAR AS editor_user," +
|
||||||
" $7::VARCHAR AS editor_service," +
|
" $7::VARCHAR AS editor_service," +
|
||||||
" IFNULL((resource_owner), $8::VARCHAR) AS resource_owner," +
|
" IFNULL((resource_owner), $8::VARCHAR) AS resource_owner," +
|
||||||
|
" $9::VARCHAR AS tenant," +
|
||||||
|
" NEXTVAL(CONCAT('eventstore.', IFNULL($9, 'system'), '_seq'))," +
|
||||||
" aggregate_sequence AS previous_aggregate_sequence," +
|
" aggregate_sequence AS previous_aggregate_sequence," +
|
||||||
" aggregate_type_sequence AS previous_aggregate_type_sequence " +
|
" aggregate_type_sequence AS previous_aggregate_type_sequence " +
|
||||||
"FROM previous_data " +
|
"FROM previous_data " +
|
||||||
"RETURNING id, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, creation_date, resource_owner"
|
"RETURNING id, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, creation_date, resource_owner, tenant"
|
||||||
|
|
||||||
uniqueInsert = `INSERT INTO eventstore.unique_constraints
|
uniqueInsert = `INSERT INTO eventstore.unique_constraints
|
||||||
(
|
(
|
||||||
@ -113,17 +120,20 @@ func (db *CRDB) Push(ctx context.Context, events []*repository.Event, uniqueCons
|
|||||||
event.EditorUser,
|
event.EditorUser,
|
||||||
event.EditorService,
|
event.EditorService,
|
||||||
event.ResourceOwner,
|
event.ResourceOwner,
|
||||||
).Scan(&event.ID, &event.Sequence, &previousAggregateSequence, &previousAggregateTypeSequence, &event.CreationDate, &event.ResourceOwner)
|
event.Tenant,
|
||||||
|
).Scan(&event.ID, &event.Sequence, &previousAggregateSequence, &previousAggregateTypeSequence, &event.CreationDate, &event.ResourceOwner, &event.Tenant)
|
||||||
|
|
||||||
event.PreviousAggregateSequence = uint64(previousAggregateSequence)
|
event.PreviousAggregateSequence = uint64(previousAggregateSequence)
|
||||||
event.PreviousAggregateTypeSequence = uint64(previousAggregateTypeSequence)
|
event.PreviousAggregateTypeSequence = uint64(previousAggregateTypeSequence)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("SQL-NOqH7",
|
logging.WithFields(
|
||||||
"aggregate", event.AggregateType,
|
"aggregate", event.AggregateType,
|
||||||
"aggregateId", event.AggregateID,
|
"aggregateId", event.AggregateID,
|
||||||
"aggregateType", event.AggregateType,
|
"aggregateType", event.AggregateType,
|
||||||
"eventType", event.Type).WithError(err).Info("query failed")
|
"eventType", event.Type,
|
||||||
|
"tenant", event.Tenant,
|
||||||
|
).WithError(err).Info("query failed")
|
||||||
return caos_errs.ThrowInternal(err, "SQL-SBP37", "unable to create event")
|
return caos_errs.ThrowInternal(err, "SQL-SBP37", "unable to create event")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,7 +162,7 @@ func (db *CRDB) handleUniqueConstraints(ctx context.Context, tx *sql.Tx, uniqueC
|
|||||||
if uniqueConstraint.Action == repository.UniqueConstraintAdd {
|
if uniqueConstraint.Action == repository.UniqueConstraintAdd {
|
||||||
_, err := tx.ExecContext(ctx, uniqueInsert, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField)
|
_, err := tx.ExecContext(ctx, uniqueInsert, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("SQL-IP3js",
|
logging.WithFields(
|
||||||
"unique_type", uniqueConstraint.UniqueType,
|
"unique_type", uniqueConstraint.UniqueType,
|
||||||
"unique_field", uniqueConstraint.UniqueField).WithError(err).Info("insert unique constraint failed")
|
"unique_field", uniqueConstraint.UniqueField).WithError(err).Info("insert unique constraint failed")
|
||||||
|
|
||||||
@ -165,7 +175,7 @@ func (db *CRDB) handleUniqueConstraints(ctx context.Context, tx *sql.Tx, uniqueC
|
|||||||
} else if uniqueConstraint.Action == repository.UniqueConstraintRemoved {
|
} else if uniqueConstraint.Action == repository.UniqueConstraintRemoved {
|
||||||
_, err := tx.ExecContext(ctx, uniqueDelete, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField)
|
_, err := tx.ExecContext(ctx, uniqueDelete, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("SQL-M0vsf",
|
logging.WithFields(
|
||||||
"unique_type", uniqueConstraint.UniqueType,
|
"unique_type", uniqueConstraint.UniqueType,
|
||||||
"unique_field", uniqueConstraint.UniqueField).WithError(err).Info("delete unique constraint failed")
|
"unique_field", uniqueConstraint.UniqueField).WithError(err).Info("delete unique constraint failed")
|
||||||
return caos_errs.ThrowInternal(err, "SQL-6n88i", "unable to remove unique constraint ")
|
return caos_errs.ThrowInternal(err, "SQL-6n88i", "unable to remove unique constraint ")
|
||||||
@ -219,6 +229,7 @@ func (db *CRDB) eventQuery() string {
|
|||||||
", editor_service" +
|
", editor_service" +
|
||||||
", editor_user" +
|
", editor_user" +
|
||||||
", resource_owner" +
|
", resource_owner" +
|
||||||
|
", tenant" +
|
||||||
", aggregate_type" +
|
", aggregate_type" +
|
||||||
", aggregate_id" +
|
", aggregate_id" +
|
||||||
", aggregate_version" +
|
", aggregate_version" +
|
||||||
@ -239,6 +250,8 @@ func (db *CRDB) columnName(col repository.Field) string {
|
|||||||
return "event_sequence"
|
return "event_sequence"
|
||||||
case repository.FieldResourceOwner:
|
case repository.FieldResourceOwner:
|
||||||
return "resource_owner"
|
return "resource_owner"
|
||||||
|
case repository.FieldTenant:
|
||||||
|
return "tenant"
|
||||||
case repository.FieldEditorService:
|
case repository.FieldEditorService:
|
||||||
return "editor_service"
|
return "editor_service"
|
||||||
case repository.FieldEditorUser:
|
case repository.FieldEditorUser:
|
||||||
|
@ -2,37 +2,31 @@ package sql
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/cockroachdb/cockroach-go/v2/testserver"
|
"github.com/cockroachdb/cockroach-go/v2/testserver"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/cmd/admin/initialise"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
migrationsPath = os.ExpandEnv("${GOPATH}/src/github.com/caos/zitadel/migrations/cockroach")
|
|
||||||
testCRDBClient *sql.DB
|
testCRDBClient *sql.DB
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
ts, err := testserver.NewTestServer()
|
ts, err := testserver.NewTestServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("REPOS-RvjLG", "error", err).Fatal("unable to start db")
|
logging.WithFields("error", err).Fatal("unable to start db")
|
||||||
}
|
}
|
||||||
|
|
||||||
testCRDBClient, err = sql.Open("postgres", ts.PGURL().String())
|
testCRDBClient, err = sql.Open("postgres", ts.PGURL().String())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("REPOS-CF6dQ", "error", err).Fatal("unable to connect to db")
|
logging.WithFields("error", err).Fatal("unable to connect to db")
|
||||||
}
|
}
|
||||||
if err = testCRDBClient.Ping(); err != nil {
|
if err = testCRDBClient.Ping(); err != nil {
|
||||||
logging.LogWithFields("REPOS-CF6dQ", "error", err).Fatal("unable to ping db")
|
logging.WithFields("error", err).Fatal("unable to ping db")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -40,113 +34,26 @@ func TestMain(m *testing.M) {
|
|||||||
ts.Stop()
|
ts.Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err = executeMigrations(); err != nil {
|
if err = initDB(testCRDBClient); err != nil {
|
||||||
logging.LogWithFields("REPOS-jehDD", "error", err).Fatal("migrations failed")
|
logging.WithFields("error", err).Fatal("migrations failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeMigrations() error {
|
func initDB(db *sql.DB) error {
|
||||||
files, err := migrationFilePaths()
|
username := "zitadel"
|
||||||
|
database := "zitadel"
|
||||||
|
err := initialise.Initialise(db, initialise.VerifyUser(username, ""),
|
||||||
|
initialise.VerifyDatabase(database),
|
||||||
|
initialise.VerifyGrant(database, username))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sort.Sort(files)
|
return initialise.VerifyZitadel(db)
|
||||||
if err = setPasswordNULL(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = createFlywayHistory(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
migrationData, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
migration := os.ExpandEnv(string(migrationData))
|
|
||||||
if _, err = testCRDBClient.Exec(migration); err != nil {
|
|
||||||
return fmt.Errorf("exec file: %v || err: %w", file, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setPasswordNULL() error {
|
|
||||||
passwordNames := []string{
|
|
||||||
"eventstorepassword",
|
|
||||||
"managementpassword",
|
|
||||||
"adminapipassword",
|
|
||||||
"authpassword",
|
|
||||||
"notificationpassword",
|
|
||||||
"authzpassword",
|
|
||||||
"queriespassword",
|
|
||||||
}
|
|
||||||
for _, name := range passwordNames {
|
|
||||||
if err := os.Setenv(name, "NULL"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createFlywayHistory() error {
|
|
||||||
_, err := testCRDBClient.Exec("CREATE TABLE defaultdb.flyway_schema_history(id TEXT, PRIMARY KEY(id));")
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillUniqueData(unique_type, field string) error {
|
func fillUniqueData(unique_type, field string) error {
|
||||||
_, err := testCRDBClient.Exec("INSERT INTO eventstore.unique_constraints (unique_type, unique_field) VALUES ($1, $2)", unique_type, field)
|
_, err := testCRDBClient.Exec("INSERT INTO eventstore.unique_constraints (unique_type, unique_field) VALUES ($1, $2)", unique_type, field)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type migrationPaths []string
|
|
||||||
|
|
||||||
type version struct {
|
|
||||||
major int
|
|
||||||
minor int
|
|
||||||
}
|
|
||||||
|
|
||||||
func versionFromPath(s string) version {
|
|
||||||
v := s[strings.Index(s, "/V")+2 : strings.Index(s, "__")]
|
|
||||||
splitted := strings.Split(v, ".")
|
|
||||||
res := version{}
|
|
||||||
var err error
|
|
||||||
if len(splitted) >= 1 {
|
|
||||||
res.major, err = strconv.Atoi(splitted[0])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(splitted) >= 2 {
|
|
||||||
res.minor, err = strconv.Atoi(splitted[1])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a migrationPaths) Len() int { return len(a) }
|
|
||||||
func (a migrationPaths) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a migrationPaths) Less(i, j int) bool {
|
|
||||||
versionI := versionFromPath(a[i])
|
|
||||||
versionJ := versionFromPath(a[j])
|
|
||||||
|
|
||||||
return versionI.major < versionJ.major ||
|
|
||||||
(versionI.major == versionJ.major && versionI.minor < versionJ.minor)
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrationFilePaths() (migrationPaths, error) {
|
|
||||||
files := make(migrationPaths, 0)
|
|
||||||
err := filepath.Walk(migrationsPath, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil || info.IsDir() || !strings.HasSuffix(info.Name(), ".sql") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
files = append(files, path)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return files, err
|
|
||||||
}
|
|
||||||
|
@ -9,9 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
|
||||||
z_errors "github.com/caos/zitadel/internal/errors"
|
z_errors "github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||||
"github.com/lib/pq"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type querier interface {
|
type querier interface {
|
||||||
@ -48,7 +49,7 @@ func query(ctx context.Context, criteria querier, searchQuery *repository.Search
|
|||||||
|
|
||||||
rows, err := criteria.db().QueryContext(ctx, query, values...)
|
rows, err := criteria.db().QueryContext(ctx, query, values...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("SQL-HP3Uk").WithError(err).Info("query failed")
|
logging.New().WithError(err).Info("query failed")
|
||||||
return z_errors.ThrowInternal(err, "SQL-KyeAx", "unable to filter events")
|
return z_errors.ThrowInternal(err, "SQL-KyeAx", "unable to filter events")
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
@ -108,13 +109,14 @@ func eventsScanner(scanner scan, dest interface{}) (err error) {
|
|||||||
&event.EditorService,
|
&event.EditorService,
|
||||||
&event.EditorUser,
|
&event.EditorUser,
|
||||||
&event.ResourceOwner,
|
&event.ResourceOwner,
|
||||||
|
&event.Tenant,
|
||||||
&event.AggregateType,
|
&event.AggregateType,
|
||||||
&event.AggregateID,
|
&event.AggregateID,
|
||||||
&event.Version,
|
&event.Version,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("SQL-3mofs").WithError(err).Warn("unable to scan row")
|
logging.New().WithError(err).Warn("unable to scan row")
|
||||||
return z_errors.ThrowInternal(err, "SQL-M0dsf", "unable to scan row")
|
return z_errors.ThrowInternal(err, "SQL-M0dsf", "unable to scan row")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +149,7 @@ func prepareCondition(criteria querier, filters [][]*repository.Filter) (clause
|
|||||||
var err error
|
var err error
|
||||||
value, err = json.Marshal(value)
|
value, err = json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("SQL-BSsNy").WithError(err).Warn("unable to marshal search value")
|
logging.New().WithError(err).Warn("unable to marshal search value")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,13 +130,13 @@ func Test_prepareColumns(t *testing.T) {
|
|||||||
dest: &[]*repository.Event{},
|
dest: &[]*repository.Event{},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
||||||
expected: []*repository.Event{
|
expected: []*repository.Event{
|
||||||
{AggregateID: "hodor", AggregateType: "user", Sequence: 5, Data: make(Data, 0)},
|
{AggregateID: "hodor", AggregateType: "user", Sequence: 5, Data: make(Data, 0)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fields: fields{
|
fields: fields{
|
||||||
dbRow: []interface{}{time.Time{}, repository.EventType(""), uint64(5), Sequence(0), Sequence(0), Data(nil), "", "", sql.NullString{String: ""}, repository.AggregateType("user"), "hodor", repository.Version("")},
|
dbRow: []interface{}{time.Time{}, repository.EventType(""), uint64(5), Sequence(0), Sequence(0), Data(nil), "", "", sql.NullString{String: ""}, sql.NullString{String: ""}, repository.AggregateType("user"), "hodor", repository.Version("")},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -146,7 +146,7 @@ func Test_prepareColumns(t *testing.T) {
|
|||||||
dest: []*repository.Event{},
|
dest: []*repository.Event{},
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
||||||
dbErr: errors.IsErrorInvalidArgument,
|
dbErr: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -158,7 +158,7 @@ func Test_prepareColumns(t *testing.T) {
|
|||||||
dbErr: sql.ErrConnDone,
|
dbErr: sql.ErrConnDone,
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
||||||
dbErr: errors.IsInternal,
|
dbErr: errors.IsInternal,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -592,7 +592,7 @@ func Test_query_events_mocked(t *testing.T) {
|
|||||||
},
|
},
|
||||||
fields: fields{
|
fields: fields{
|
||||||
mock: newMockClient(t).expectQuery(t,
|
mock: newMockClient(t).expectQuery(t,
|
||||||
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence DESC`,
|
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence DESC`,
|
||||||
[]driver.Value{repository.AggregateType("user")},
|
[]driver.Value{repository.AggregateType("user")},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -621,7 +621,7 @@ func Test_query_events_mocked(t *testing.T) {
|
|||||||
},
|
},
|
||||||
fields: fields{
|
fields: fields{
|
||||||
mock: newMockClient(t).expectQuery(t,
|
mock: newMockClient(t).expectQuery(t,
|
||||||
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence LIMIT \$2`,
|
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence LIMIT \$2`,
|
||||||
[]driver.Value{repository.AggregateType("user"), uint64(5)},
|
[]driver.Value{repository.AggregateType("user"), uint64(5)},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -650,7 +650,7 @@ func Test_query_events_mocked(t *testing.T) {
|
|||||||
},
|
},
|
||||||
fields: fields{
|
fields: fields{
|
||||||
mock: newMockClient(t).expectQuery(t,
|
mock: newMockClient(t).expectQuery(t,
|
||||||
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence DESC LIMIT \$2`,
|
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence DESC LIMIT \$2`,
|
||||||
[]driver.Value{repository.AggregateType("user"), uint64(5)},
|
[]driver.Value{repository.AggregateType("user"), uint64(5)},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -679,7 +679,7 @@ func Test_query_events_mocked(t *testing.T) {
|
|||||||
},
|
},
|
||||||
fields: fields{
|
fields: fields{
|
||||||
mock: newMockClient(t).expectQueryErr(t,
|
mock: newMockClient(t).expectQueryErr(t,
|
||||||
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence DESC`,
|
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence DESC`,
|
||||||
[]driver.Value{repository.AggregateType("user")},
|
[]driver.Value{repository.AggregateType("user")},
|
||||||
sql.ErrConnDone),
|
sql.ErrConnDone),
|
||||||
},
|
},
|
||||||
@ -708,7 +708,7 @@ func Test_query_events_mocked(t *testing.T) {
|
|||||||
},
|
},
|
||||||
fields: fields{
|
fields: fields{
|
||||||
mock: newMockClient(t).expectQuery(t,
|
mock: newMockClient(t).expectQuery(t,
|
||||||
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence DESC`,
|
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) ORDER BY event_sequence DESC`,
|
||||||
[]driver.Value{repository.AggregateType("user")},
|
[]driver.Value{repository.AggregateType("user")},
|
||||||
&repository.Event{Sequence: 100}),
|
&repository.Event{Sequence: 100}),
|
||||||
},
|
},
|
||||||
@ -776,7 +776,7 @@ func Test_query_events_mocked(t *testing.T) {
|
|||||||
},
|
},
|
||||||
fields: fields{
|
fields: fields{
|
||||||
mock: newMockClient(t).expectQuery(t,
|
mock: newMockClient(t).expectQuery(t,
|
||||||
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) OR \( aggregate_type = \$2 AND aggregate_id = \$3 \) ORDER BY event_sequence DESC LIMIT \$4`,
|
`SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, previous_aggregate_type_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE \( aggregate_type = \$1 \) OR \( aggregate_type = \$2 AND aggregate_id = \$3 \) ORDER BY event_sequence DESC LIMIT \$4`,
|
||||||
[]driver.Value{repository.AggregateType("user"), repository.AggregateType("org"), "asdf42", uint64(5)},
|
[]driver.Value{repository.AggregateType("user"), repository.AggregateType("org"), "asdf42", uint64(5)},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -52,7 +52,7 @@ func (db *CRDB) Step20(ctx context.Context, latestSequence uint64) error {
|
|||||||
if err = tx.Commit(); err != nil {
|
if err = tx.Commit(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logging.LogWithFields("SQL-bXVwS", "currentSeq", currentSequence, "events", len(events)).Info("events updated")
|
logging.WithFields("currentSeq", currentSequence, "events", len(events)).Info("events updated")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ type SearchQueryBuilder struct {
|
|||||||
limit uint64
|
limit uint64
|
||||||
desc bool
|
desc bool
|
||||||
resourceOwner string
|
resourceOwner string
|
||||||
|
tenant string
|
||||||
queries []*SearchQuery
|
queries []*SearchQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +68,12 @@ func (factory *SearchQueryBuilder) ResourceOwner(resourceOwner string) *SearchQu
|
|||||||
return factory
|
return factory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Tenant defines the tenant (system) of the events
|
||||||
|
func (factory *SearchQueryBuilder) Tenant(tenant string) *SearchQueryBuilder {
|
||||||
|
factory.tenant = tenant
|
||||||
|
return factory
|
||||||
|
}
|
||||||
|
|
||||||
//OrderDesc changes the sorting order of the returned events to descending
|
//OrderDesc changes the sorting order of the returned events to descending
|
||||||
func (factory *SearchQueryBuilder) OrderDesc() *SearchQueryBuilder {
|
func (factory *SearchQueryBuilder) OrderDesc() *SearchQueryBuilder {
|
||||||
factory.desc = true
|
factory.desc = true
|
||||||
@ -138,12 +145,13 @@ func (query *SearchQuery) Builder() *SearchQueryBuilder {
|
|||||||
return query.builder
|
return query.builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (builder *SearchQueryBuilder) build() (*repository.SearchQuery, error) {
|
func (builder *SearchQueryBuilder) build(tenantID string) (*repository.SearchQuery, error) {
|
||||||
if builder == nil ||
|
if builder == nil ||
|
||||||
len(builder.queries) < 1 ||
|
len(builder.queries) < 1 ||
|
||||||
builder.columns.Validate() != nil {
|
builder.columns.Validate() != nil {
|
||||||
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-4m9gs", "builder invalid")
|
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-4m9gs", "builder invalid")
|
||||||
}
|
}
|
||||||
|
builder.tenant = tenantID
|
||||||
filters := make([][]*repository.Filter, len(builder.queries))
|
filters := make([][]*repository.Filter, len(builder.queries))
|
||||||
|
|
||||||
for i, query := range builder.queries {
|
for i, query := range builder.queries {
|
||||||
@ -155,6 +163,7 @@ func (builder *SearchQueryBuilder) build() (*repository.SearchQuery, error) {
|
|||||||
query.eventSequenceGreaterFilter,
|
query.eventSequenceGreaterFilter,
|
||||||
query.eventSequenceLessFilter,
|
query.eventSequenceLessFilter,
|
||||||
query.builder.resourceOwnerFilter,
|
query.builder.resourceOwnerFilter,
|
||||||
|
query.builder.tenantFilter,
|
||||||
} {
|
} {
|
||||||
if filter := f(); filter != nil {
|
if filter := f(); filter != nil {
|
||||||
if err := filter.Validate(); err != nil {
|
if err := filter.Validate(); err != nil {
|
||||||
@ -238,6 +247,13 @@ func (builder *SearchQueryBuilder) resourceOwnerFilter() *repository.Filter {
|
|||||||
return repository.NewFilter(repository.FieldResourceOwner, builder.resourceOwner, repository.OperationEquals)
|
return repository.NewFilter(repository.FieldResourceOwner, builder.resourceOwner, repository.OperationEquals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (builder *SearchQueryBuilder) tenantFilter() *repository.Filter {
|
||||||
|
if builder.tenant == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return repository.NewFilter(repository.FieldTenant, builder.tenant, repository.OperationEquals)
|
||||||
|
}
|
||||||
|
|
||||||
func (query *SearchQuery) eventDataFilter() *repository.Filter {
|
func (query *SearchQuery) eventDataFilter() *repository.Filter {
|
||||||
if len(query.eventData) == 0 {
|
if len(query.eventData) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -226,6 +226,7 @@ func TestSearchQuerybuilderBuild(t *testing.T) {
|
|||||||
type args struct {
|
type args struct {
|
||||||
columns Columns
|
columns Columns
|
||||||
setters []func(*SearchQueryBuilder) *SearchQueryBuilder
|
setters []func(*SearchQueryBuilder) *SearchQueryBuilder
|
||||||
|
tenant string
|
||||||
}
|
}
|
||||||
type res struct {
|
type res struct {
|
||||||
isErr func(err error) bool
|
isErr func(err error) bool
|
||||||
@ -620,6 +621,32 @@ func TestSearchQuerybuilderBuild(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "filter aggregate type and tenant",
|
||||||
|
args: args{
|
||||||
|
columns: ColumnsEvent,
|
||||||
|
setters: []func(*SearchQueryBuilder) *SearchQueryBuilder{
|
||||||
|
testAddQuery(
|
||||||
|
testSetAggregateTypes("user"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
tenant: "tenant",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
isErr: nil,
|
||||||
|
query: &repository.SearchQuery{
|
||||||
|
Columns: repository.ColumnsEvent,
|
||||||
|
Desc: false,
|
||||||
|
Limit: 0,
|
||||||
|
Filters: [][]*repository.Filter{
|
||||||
|
{
|
||||||
|
repository.NewFilter(repository.FieldAggregateType, repository.AggregateType("user"), repository.OperationEquals),
|
||||||
|
repository.NewFilter(repository.FieldTenant, "tenant", repository.OperationEquals),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "column invalid",
|
name: "column invalid",
|
||||||
args: args{
|
args: args{
|
||||||
@ -641,7 +668,7 @@ func TestSearchQuerybuilderBuild(t *testing.T) {
|
|||||||
for _, f := range tt.args.setters {
|
for _, f := range tt.args.setters {
|
||||||
builder = f(builder)
|
builder = f(builder)
|
||||||
}
|
}
|
||||||
query, err := builder.build()
|
query, err := builder.build(tt.args.tenant)
|
||||||
if tt.res.isErr != nil && !tt.res.isErr(err) {
|
if tt.res.isErr != nil && !tt.res.isErr(err) {
|
||||||
t.Errorf("wrong error(%T): %v", err, err)
|
t.Errorf("wrong error(%T): %v", err, err)
|
||||||
return
|
return
|
||||||
|
@ -122,6 +122,7 @@ func mapEventToV1Event(event Event) *models.Event {
|
|||||||
AggregateType: models.AggregateType(event.Aggregate().Type),
|
AggregateType: models.AggregateType(event.Aggregate().Type),
|
||||||
AggregateID: event.Aggregate().ID,
|
AggregateID: event.Aggregate().ID,
|
||||||
ResourceOwner: event.Aggregate().ResourceOwner,
|
ResourceOwner: event.Aggregate().ResourceOwner,
|
||||||
|
Tenant: event.Aggregate().Tenant,
|
||||||
EditorService: event.EditorService(),
|
EditorService: event.EditorService(),
|
||||||
EditorUser: event.EditorUser(),
|
EditorUser: event.EditorUser(),
|
||||||
Data: event.DataAsBytes(),
|
Data: event.DataAsBytes(),
|
||||||
|
@ -11,11 +11,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
selectEscaped = `SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore\.events WHERE aggregate_type = \$1`
|
selectEscaped = `SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore\.events WHERE aggregate_type = \$1`
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
eventColumns = []string{"creation_date", "event_type", "event_sequence", "previous_aggregate_sequence", "event_data", "editor_service", "editor_user", "resource_owner", "aggregate_type", "aggregate_id", "aggregate_version"}
|
eventColumns = []string{"creation_date", "event_type", "event_sequence", "previous_aggregate_sequence", "event_data", "editor_service", "editor_user", "resource_owner", "tenant", "aggregate_type", "aggregate_id", "aggregate_version"}
|
||||||
expectedFilterEventsLimitFormat = regexp.MustCompile(selectEscaped + ` ORDER BY event_sequence LIMIT \$2`).String()
|
expectedFilterEventsLimitFormat = regexp.MustCompile(selectEscaped + ` ORDER BY event_sequence LIMIT \$2`).String()
|
||||||
expectedFilterEventsDescFormat = regexp.MustCompile(selectEscaped + ` ORDER BY event_sequence DESC`).String()
|
expectedFilterEventsDescFormat = regexp.MustCompile(selectEscaped + ` ORDER BY event_sequence DESC`).String()
|
||||||
expectedFilterEventsAggregateIDLimit = regexp.MustCompile(selectEscaped + ` AND aggregate_id = \$2 ORDER BY event_sequence LIMIT \$3`).String()
|
expectedFilterEventsAggregateIDLimit = regexp.MustCompile(selectEscaped + ` AND aggregate_id = \$2 ORDER BY event_sequence LIMIT \$3`).String()
|
||||||
@ -23,10 +23,10 @@ var (
|
|||||||
expectedGetAllEvents = regexp.MustCompile(selectEscaped + ` ORDER BY event_sequence`).String()
|
expectedGetAllEvents = regexp.MustCompile(selectEscaped + ` ORDER BY event_sequence`).String()
|
||||||
|
|
||||||
expectedInsertStatement = regexp.MustCompile(`INSERT INTO eventstore\.events ` +
|
expectedInsertStatement = regexp.MustCompile(`INSERT INTO eventstore\.events ` +
|
||||||
`\(event_type, aggregate_type, aggregate_id, aggregate_version, creation_date, event_data, editor_user, editor_service, resource_owner, previous_aggregate_sequence, previous_aggregate_type_sequence\) ` +
|
`\(event_type, aggregate_type, aggregate_id, aggregate_version, creation_date, event_data, editor_user, editor_service, resource_owner, tenant, previous_aggregate_sequence, previous_aggregate_type_sequence\) ` +
|
||||||
`SELECT \$1, \$2, \$3, \$4, COALESCE\(\$5, now\(\)\), \$6, \$7, \$8, \$9, \$10 ` +
|
`SELECT \$1, \$2, \$3, \$4, COALESCE\(\$5, now\(\)\), \$6, \$7, \$8, \$9, \$10, \$11 ` +
|
||||||
`WHERE EXISTS \(` +
|
`WHERE EXISTS \(` +
|
||||||
`SELECT 1 FROM eventstore\.events WHERE aggregate_type = \$11 AND aggregate_id = \$12 HAVING MAX\(event_sequence\) = \$13 OR \(\$14::BIGINT IS NULL AND COUNT\(\*\) = 0\)\) ` +
|
`SELECT 1 FROM eventstore\.events WHERE aggregate_type = \$12 AND aggregate_id = \$13 HAVING MAX\(event_sequence\) = \$14 OR \(\$14::BIGINT IS NULL AND COUNT\(\*\) = 0\)\) ` +
|
||||||
`RETURNING event_sequence, creation_date`).String()
|
`RETURNING event_sequence, creation_date`).String()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -99,7 +99,7 @@ func (db *dbMock) expectRollback(err error) *dbMock {
|
|||||||
func (db *dbMock) expectInsertEvent(e *models.Event, returnedSequence uint64) *dbMock {
|
func (db *dbMock) expectInsertEvent(e *models.Event, returnedSequence uint64) *dbMock {
|
||||||
db.mock.ExpectQuery(expectedInsertStatement).
|
db.mock.ExpectQuery(expectedInsertStatement).
|
||||||
WithArgs(
|
WithArgs(
|
||||||
e.Type, e.AggregateType, e.AggregateID, e.AggregateVersion, sqlmock.AnyArg(), Data(e.Data), e.EditorUser, e.EditorService, e.ResourceOwner, Sequence(e.PreviousSequence),
|
e.Type, e.AggregateType, e.AggregateID, e.AggregateVersion, sqlmock.AnyArg(), Data(e.Data), e.EditorUser, e.EditorService, e.ResourceOwner, e.Tenant, Sequence(e.PreviousSequence),
|
||||||
e.AggregateType, e.AggregateID, Sequence(e.PreviousSequence), Sequence(e.PreviousSequence),
|
e.AggregateType, e.AggregateID, Sequence(e.PreviousSequence), Sequence(e.PreviousSequence),
|
||||||
).
|
).
|
||||||
WillReturnRows(
|
WillReturnRows(
|
||||||
@ -113,7 +113,7 @@ func (db *dbMock) expectInsertEvent(e *models.Event, returnedSequence uint64) *d
|
|||||||
func (db *dbMock) expectInsertEventError(e *models.Event) *dbMock {
|
func (db *dbMock) expectInsertEventError(e *models.Event) *dbMock {
|
||||||
db.mock.ExpectQuery(expectedInsertStatement).
|
db.mock.ExpectQuery(expectedInsertStatement).
|
||||||
WithArgs(
|
WithArgs(
|
||||||
e.Type, e.AggregateType, e.AggregateID, e.AggregateVersion, sqlmock.AnyArg(), Data(e.Data), e.EditorUser, e.EditorService, e.ResourceOwner, Sequence(e.PreviousSequence),
|
e.Type, e.AggregateType, e.AggregateID, e.AggregateVersion, sqlmock.AnyArg(), Data(e.Data), e.EditorUser, e.EditorService, e.ResourceOwner, e.Tenant, Sequence(e.PreviousSequence),
|
||||||
e.AggregateType, e.AggregateID, Sequence(e.PreviousSequence), Sequence(e.PreviousSequence),
|
e.AggregateType, e.AggregateID, Sequence(e.PreviousSequence), Sequence(e.PreviousSequence),
|
||||||
).
|
).
|
||||||
WillReturnError(sql.ErrTxDone)
|
WillReturnError(sql.ErrTxDone)
|
||||||
@ -124,7 +124,7 @@ func (db *dbMock) expectInsertEventError(e *models.Event) *dbMock {
|
|||||||
func (db *dbMock) expectFilterEventsLimit(aggregateType string, limit uint64, eventCount int) *dbMock {
|
func (db *dbMock) expectFilterEventsLimit(aggregateType string, limit uint64, eventCount int) *dbMock {
|
||||||
rows := sqlmock.NewRows(eventColumns)
|
rows := sqlmock.NewRows(eventColumns)
|
||||||
for i := 0; i < eventCount; i++ {
|
for i := 0; i < eventCount; i++ {
|
||||||
rows.AddRow(time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "aggType", "aggID", "v1.0.0")
|
rows.AddRow(time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "tenant", "aggType", "aggID", "v1.0.0")
|
||||||
}
|
}
|
||||||
db.mock.ExpectQuery(expectedFilterEventsLimitFormat).
|
db.mock.ExpectQuery(expectedFilterEventsLimitFormat).
|
||||||
WithArgs(aggregateType, limit).
|
WithArgs(aggregateType, limit).
|
||||||
@ -135,7 +135,7 @@ func (db *dbMock) expectFilterEventsLimit(aggregateType string, limit uint64, ev
|
|||||||
func (db *dbMock) expectFilterEventsDesc(aggregateType string, eventCount int) *dbMock {
|
func (db *dbMock) expectFilterEventsDesc(aggregateType string, eventCount int) *dbMock {
|
||||||
rows := sqlmock.NewRows(eventColumns)
|
rows := sqlmock.NewRows(eventColumns)
|
||||||
for i := eventCount; i > 0; i-- {
|
for i := eventCount; i > 0; i-- {
|
||||||
rows.AddRow(time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "aggType", "aggID", "v1.0.0")
|
rows.AddRow(time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "tenant", "aggType", "aggID", "v1.0.0")
|
||||||
}
|
}
|
||||||
db.mock.ExpectQuery(expectedFilterEventsDescFormat).
|
db.mock.ExpectQuery(expectedFilterEventsDescFormat).
|
||||||
WillReturnRows(rows)
|
WillReturnRows(rows)
|
||||||
@ -145,7 +145,7 @@ func (db *dbMock) expectFilterEventsDesc(aggregateType string, eventCount int) *
|
|||||||
func (db *dbMock) expectFilterEventsAggregateIDLimit(aggregateType, aggregateID string, limit uint64) *dbMock {
|
func (db *dbMock) expectFilterEventsAggregateIDLimit(aggregateType, aggregateID string, limit uint64) *dbMock {
|
||||||
rows := sqlmock.NewRows(eventColumns)
|
rows := sqlmock.NewRows(eventColumns)
|
||||||
for i := limit; i > 0; i-- {
|
for i := limit; i > 0; i-- {
|
||||||
rows.AddRow(time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "aggType", "aggID", "v1.0.0")
|
rows.AddRow(time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "tenant", "aggType", "aggID", "v1.0.0")
|
||||||
}
|
}
|
||||||
db.mock.ExpectQuery(expectedFilterEventsAggregateIDLimit).
|
db.mock.ExpectQuery(expectedFilterEventsAggregateIDLimit).
|
||||||
WithArgs(aggregateType, aggregateID, limit).
|
WithArgs(aggregateType, aggregateID, limit).
|
||||||
@ -156,7 +156,7 @@ func (db *dbMock) expectFilterEventsAggregateIDLimit(aggregateType, aggregateID
|
|||||||
func (db *dbMock) expectFilterEventsAggregateIDTypeLimit(aggregateType, aggregateID string, limit uint64) *dbMock {
|
func (db *dbMock) expectFilterEventsAggregateIDTypeLimit(aggregateType, aggregateID string, limit uint64) *dbMock {
|
||||||
rows := sqlmock.NewRows(eventColumns)
|
rows := sqlmock.NewRows(eventColumns)
|
||||||
for i := limit; i > 0; i-- {
|
for i := limit; i > 0; i-- {
|
||||||
rows.AddRow(time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "aggType", "aggID", "v1.0.0")
|
rows.AddRow(time.Now(), "eventType", Sequence(i+1), Sequence(i), nil, "svc", "hodor", "org", "tenant", "aggType", "aggID", "v1.0.0")
|
||||||
}
|
}
|
||||||
db.mock.ExpectQuery(expectedFilterEventsAggregateIDTypeLimit).
|
db.mock.ExpectQuery(expectedFilterEventsAggregateIDTypeLimit).
|
||||||
WithArgs(aggregateType, aggregateID, limit).
|
WithArgs(aggregateType, aggregateID, limit).
|
||||||
|
@ -26,7 +26,7 @@ func filter(querier Querier, searchQuery *es_models.SearchQueryFactory) (events
|
|||||||
|
|
||||||
rows, err := querier.Query(query, values...)
|
rows, err := querier.Query(query, values...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("SQL-HP3Uk").WithError(err).Info("query failed")
|
logging.New().WithError(err).Info("query failed")
|
||||||
return nil, errors.ThrowInternal(err, "SQL-IJuyR", "unable to filter events")
|
return nil, errors.ThrowInternal(err, "SQL-IJuyR", "unable to filter events")
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
@ -55,7 +55,7 @@ func (db *SQL) LatestSequence(ctx context.Context, queryFactory *es_models.Searc
|
|||||||
sequence := new(Sequence)
|
sequence := new(Sequence)
|
||||||
err := rowScanner(row.Scan, sequence)
|
err := rowScanner(row.Scan, sequence)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("SQL-WsxTg").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Info("query failed")
|
logging.New().WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Info("query failed")
|
||||||
return 0, errors.ThrowInternal(err, "SQL-Yczyx", "unable to filter latest sequence")
|
return 0, errors.ThrowInternal(err, "SQL-Yczyx", "unable to filter latest sequence")
|
||||||
}
|
}
|
||||||
return uint64(*sequence), nil
|
return uint64(*sequence), nil
|
||||||
|
@ -23,6 +23,7 @@ const (
|
|||||||
", editor_service" +
|
", editor_service" +
|
||||||
", editor_user" +
|
", editor_user" +
|
||||||
", resource_owner" +
|
", resource_owner" +
|
||||||
|
", tenant" +
|
||||||
", aggregate_type" +
|
", aggregate_type" +
|
||||||
", aggregate_id" +
|
", aggregate_id" +
|
||||||
", aggregate_version" +
|
", aggregate_version" +
|
||||||
@ -32,7 +33,7 @@ const (
|
|||||||
func buildQuery(queryFactory *es_models.SearchQueryFactory) (query string, limit uint64, values []interface{}, rowScanner func(s scan, dest interface{}) error) {
|
func buildQuery(queryFactory *es_models.SearchQueryFactory) (query string, limit uint64, values []interface{}, rowScanner func(s scan, dest interface{}) error) {
|
||||||
searchQuery, err := queryFactory.Build()
|
searchQuery, err := queryFactory.Build()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("SQL-cshKu").WithError(err).Warn("search query factory invalid")
|
logging.New().WithError(err).Warn("search query factory invalid")
|
||||||
return "", 0, nil, nil
|
return "", 0, nil, nil
|
||||||
}
|
}
|
||||||
query, rowScanner = prepareColumns(searchQuery.Columns)
|
query, rowScanner = prepareColumns(searchQuery.Columns)
|
||||||
@ -116,13 +117,14 @@ func prepareColumns(columns es_models.Columns) (string, func(s scan, dest interf
|
|||||||
&event.EditorService,
|
&event.EditorService,
|
||||||
&event.EditorUser,
|
&event.EditorUser,
|
||||||
&event.ResourceOwner,
|
&event.ResourceOwner,
|
||||||
|
&event.Tenant,
|
||||||
&event.AggregateType,
|
&event.AggregateType,
|
||||||
&event.AggregateID,
|
&event.AggregateID,
|
||||||
&event.AggregateVersion,
|
&event.AggregateVersion,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("SQL-kn1Sw").WithError(err).Warn("unable to scan row")
|
logging.New().WithError(err).Warn("unable to scan row")
|
||||||
return z_errors.ThrowInternal(err, "SQL-J0hFS", "unable to scan row")
|
return z_errors.ThrowInternal(err, "SQL-J0hFS", "unable to scan row")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,6 +177,8 @@ func getField(field es_models.Field) string {
|
|||||||
return "event_sequence"
|
return "event_sequence"
|
||||||
case es_models.Field_ResourceOwner:
|
case es_models.Field_ResourceOwner:
|
||||||
return "resource_owner"
|
return "resource_owner"
|
||||||
|
case es_models.Field_Tenant:
|
||||||
|
return "tenant"
|
||||||
case es_models.Field_EditorService:
|
case es_models.Field_EditorService:
|
||||||
return "editor_service"
|
return "editor_service"
|
||||||
case es_models.Field_EditorUser:
|
case es_models.Field_EditorUser:
|
||||||
|
@ -80,6 +80,7 @@ func Test_getField(t *testing.T) {
|
|||||||
es_models.Field_AggregateID: "aggregate_id",
|
es_models.Field_AggregateID: "aggregate_id",
|
||||||
es_models.Field_LatestSequence: "event_sequence",
|
es_models.Field_LatestSequence: "event_sequence",
|
||||||
es_models.Field_ResourceOwner: "resource_owner",
|
es_models.Field_ResourceOwner: "resource_owner",
|
||||||
|
es_models.Field_Tenant: "tenant",
|
||||||
es_models.Field_EditorService: "editor_service",
|
es_models.Field_EditorService: "editor_service",
|
||||||
es_models.Field_EditorUser: "editor_user",
|
es_models.Field_EditorUser: "editor_user",
|
||||||
es_models.Field_EventType: "event_type",
|
es_models.Field_EventType: "event_type",
|
||||||
@ -234,8 +235,8 @@ func Test_prepareColumns(t *testing.T) {
|
|||||||
dest: new(es_models.Event),
|
dest: new(es_models.Event),
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
||||||
dbRow: []interface{}{time.Time{}, es_models.EventType(""), uint64(5), Sequence(0), Data(nil), "", "", "", es_models.AggregateType("user"), "hodor", es_models.Version("")},
|
dbRow: []interface{}{time.Time{}, es_models.EventType(""), uint64(5), Sequence(0), Data(nil), "", "", "", "", es_models.AggregateType("user"), "hodor", es_models.Version("")},
|
||||||
expected: es_models.Event{AggregateID: "hodor", AggregateType: "user", Sequence: 5, Data: make(Data, 0)},
|
expected: es_models.Event{AggregateID: "hodor", AggregateType: "user", Sequence: 5, Data: make(Data, 0)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -246,7 +247,7 @@ func Test_prepareColumns(t *testing.T) {
|
|||||||
dest: new(uint64),
|
dest: new(uint64),
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
||||||
dbErr: errors.IsErrorInvalidArgument,
|
dbErr: errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -258,7 +259,7 @@ func Test_prepareColumns(t *testing.T) {
|
|||||||
dbErr: sql.ErrConnDone,
|
dbErr: sql.ErrConnDone,
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events",
|
||||||
dbErr: errors.IsInternal,
|
dbErr: errors.IsInternal,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -429,7 +430,7 @@ func Test_buildQuery(t *testing.T) {
|
|||||||
queryFactory: es_models.NewSearchQueryFactory("user").OrderDesc(),
|
queryFactory: es_models.NewSearchQueryFactory("user").OrderDesc(),
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE aggregate_type = $1 ORDER BY event_sequence DESC",
|
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE aggregate_type = $1 ORDER BY event_sequence DESC",
|
||||||
rowScanner: true,
|
rowScanner: true,
|
||||||
values: []interface{}{es_models.AggregateType("user")},
|
values: []interface{}{es_models.AggregateType("user")},
|
||||||
},
|
},
|
||||||
@ -440,7 +441,7 @@ func Test_buildQuery(t *testing.T) {
|
|||||||
queryFactory: es_models.NewSearchQueryFactory("user").Limit(5),
|
queryFactory: es_models.NewSearchQueryFactory("user").Limit(5),
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE aggregate_type = $1 ORDER BY event_sequence LIMIT $2",
|
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE aggregate_type = $1 ORDER BY event_sequence LIMIT $2",
|
||||||
rowScanner: true,
|
rowScanner: true,
|
||||||
values: []interface{}{es_models.AggregateType("user"), uint64(5)},
|
values: []interface{}{es_models.AggregateType("user"), uint64(5)},
|
||||||
limit: 5,
|
limit: 5,
|
||||||
@ -452,7 +453,7 @@ func Test_buildQuery(t *testing.T) {
|
|||||||
queryFactory: es_models.NewSearchQueryFactory("user").Limit(5).OrderDesc(),
|
queryFactory: es_models.NewSearchQueryFactory("user").Limit(5).OrderDesc(),
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE aggregate_type = $1 ORDER BY event_sequence DESC LIMIT $2",
|
query: "SELECT creation_date, event_type, event_sequence, previous_aggregate_sequence, event_data, editor_service, editor_user, resource_owner, tenant, aggregate_type, aggregate_id, aggregate_version FROM eventstore.events WHERE aggregate_type = $1 ORDER BY event_sequence DESC LIMIT $2",
|
||||||
rowScanner: true,
|
rowScanner: true,
|
||||||
values: []interface{}{es_models.AggregateType("user"), uint64(5)},
|
values: []interface{}{es_models.AggregateType("user"), uint64(5)},
|
||||||
limit: 5,
|
limit: 5,
|
||||||
|
@ -23,6 +23,7 @@ type Aggregate struct {
|
|||||||
editorService string
|
editorService string
|
||||||
editorUser string
|
editorUser string
|
||||||
resourceOwner string
|
resourceOwner string
|
||||||
|
tenant string
|
||||||
Events []*Event
|
Events []*Event
|
||||||
Precondition *precondition
|
Precondition *precondition
|
||||||
}
|
}
|
||||||
@ -55,6 +56,7 @@ func (a *Aggregate) AppendEvent(typ EventType, payload interface{}) (*Aggregate,
|
|||||||
EditorService: a.editorService,
|
EditorService: a.editorService,
|
||||||
EditorUser: a.editorUser,
|
EditorUser: a.editorUser,
|
||||||
ResourceOwner: a.resourceOwner,
|
ResourceOwner: a.resourceOwner,
|
||||||
|
Tenant: a.tenant,
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Events = append(a.Events, e)
|
a.Events = append(a.Events, e)
|
||||||
|
@ -20,6 +20,7 @@ func (c *AggregateCreator) NewAggregate(ctx context.Context, id string, typ Aggr
|
|||||||
ctxData := authz.GetCtxData(ctx)
|
ctxData := authz.GetCtxData(ctx)
|
||||||
editorUser := ctxData.UserID
|
editorUser := ctxData.UserID
|
||||||
resourceOwner := ctxData.OrgID
|
resourceOwner := ctxData.OrgID
|
||||||
|
tenant := ctxData.TenantID
|
||||||
|
|
||||||
aggregate := &Aggregate{
|
aggregate := &Aggregate{
|
||||||
ID: id,
|
ID: id,
|
||||||
@ -30,6 +31,7 @@ func (c *AggregateCreator) NewAggregate(ctx context.Context, id string, typ Aggr
|
|||||||
editorService: c.serviceName,
|
editorService: c.serviceName,
|
||||||
editorUser: editorUser,
|
editorUser: editorUser,
|
||||||
resourceOwner: resourceOwner,
|
resourceOwner: resourceOwner,
|
||||||
|
tenant: tenant,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
|
@ -28,6 +28,7 @@ type Event struct {
|
|||||||
EditorService string
|
EditorService string
|
||||||
EditorUser string
|
EditorUser string
|
||||||
ResourceOwner string
|
ResourceOwner string
|
||||||
|
Tenant string
|
||||||
}
|
}
|
||||||
|
|
||||||
func eventData(i interface{}) ([]byte, error) {
|
func eventData(i interface{}) ([]byte, error) {
|
||||||
|
@ -11,4 +11,5 @@ const (
|
|||||||
Field_EditorUser
|
Field_EditorUser
|
||||||
Field_EventType
|
Field_EventType
|
||||||
Field_CreationDate
|
Field_CreationDate
|
||||||
|
Field_Tenant
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,7 @@ type ObjectRoot struct {
|
|||||||
AggregateID string `json:"-"`
|
AggregateID string `json:"-"`
|
||||||
Sequence uint64 `json:"-"`
|
Sequence uint64 `json:"-"`
|
||||||
ResourceOwner string `json:"-"`
|
ResourceOwner string `json:"-"`
|
||||||
|
Tenant string `json:"-"`
|
||||||
CreationDate time.Time `json:"-"`
|
CreationDate time.Time `json:"-"`
|
||||||
ChangeDate time.Time `json:"-"`
|
ChangeDate time.Time `json:"-"`
|
||||||
}
|
}
|
||||||
@ -21,6 +22,9 @@ func (o *ObjectRoot) AppendEvent(event *Event) {
|
|||||||
if o.ResourceOwner == "" {
|
if o.ResourceOwner == "" {
|
||||||
o.ResourceOwner = event.ResourceOwner
|
o.ResourceOwner = event.ResourceOwner
|
||||||
}
|
}
|
||||||
|
if o.Tenant == "" {
|
||||||
|
o.Tenant = event.Tenant
|
||||||
|
}
|
||||||
|
|
||||||
o.ChangeDate = event.CreationDate
|
o.ChangeDate = event.CreationDate
|
||||||
if o.CreationDate.IsZero() {
|
if o.CreationDate.IsZero() {
|
||||||
|
@ -17,6 +17,7 @@ type SearchQueryFactory struct {
|
|||||||
sequenceTo uint64
|
sequenceTo uint64
|
||||||
eventTypes []EventType
|
eventTypes []EventType
|
||||||
resourceOwner string
|
resourceOwner string
|
||||||
|
tenant string
|
||||||
creationDate time.Time
|
creationDate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +63,8 @@ func FactoryFromSearchQuery(query *SearchQuery) *SearchQueryFactory {
|
|||||||
}
|
}
|
||||||
case Field_ResourceOwner:
|
case Field_ResourceOwner:
|
||||||
factory = factory.ResourceOwner(filter.value.(string))
|
factory = factory.ResourceOwner(filter.value.(string))
|
||||||
|
case Field_Tenant:
|
||||||
|
factory = factory.Tenant(filter.value.(string))
|
||||||
case Field_EventType:
|
case Field_EventType:
|
||||||
factory = factory.EventTypes(filter.value.([]EventType)...)
|
factory = factory.EventTypes(filter.value.([]EventType)...)
|
||||||
case Field_EditorService, Field_EditorUser:
|
case Field_EditorService, Field_EditorUser:
|
||||||
@ -120,6 +123,11 @@ func (factory *SearchQueryFactory) ResourceOwner(resourceOwner string) *SearchQu
|
|||||||
return factory
|
return factory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (factory *SearchQueryFactory) Tenant(tenant string) *SearchQueryFactory {
|
||||||
|
factory.tenant = tenant
|
||||||
|
return factory
|
||||||
|
}
|
||||||
|
|
||||||
func (factory *SearchQueryFactory) CreationDateNewer(time time.Time) *SearchQueryFactory {
|
func (factory *SearchQueryFactory) CreationDateNewer(time time.Time) *SearchQueryFactory {
|
||||||
factory.creationDate = time
|
factory.creationDate = time
|
||||||
return factory
|
return factory
|
||||||
@ -151,6 +159,7 @@ func (factory *SearchQueryFactory) Build() (*searchQuery, error) {
|
|||||||
factory.sequenceToFilter,
|
factory.sequenceToFilter,
|
||||||
factory.eventTypeFilter,
|
factory.eventTypeFilter,
|
||||||
factory.resourceOwnerFilter,
|
factory.resourceOwnerFilter,
|
||||||
|
factory.tenantFilter,
|
||||||
factory.creationDateNewerFilter,
|
factory.creationDateNewerFilter,
|
||||||
} {
|
} {
|
||||||
if filter := f(); filter != nil {
|
if filter := f(); filter != nil {
|
||||||
@ -222,6 +231,13 @@ func (factory *SearchQueryFactory) resourceOwnerFilter() *Filter {
|
|||||||
return NewFilter(Field_ResourceOwner, factory.resourceOwner, Operation_Equals)
|
return NewFilter(Field_ResourceOwner, factory.resourceOwner, Operation_Equals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (factory *SearchQueryFactory) tenantFilter() *Filter {
|
||||||
|
if factory.tenant == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return NewFilter(Field_Tenant, factory.tenant, Operation_Equals)
|
||||||
|
}
|
||||||
|
|
||||||
func (factory *SearchQueryFactory) creationDateNewerFilter() *Filter {
|
func (factory *SearchQueryFactory) creationDateNewerFilter() *Filter {
|
||||||
if factory.creationDate.IsZero() {
|
if factory.creationDate.IsZero() {
|
||||||
return nil
|
return nil
|
||||||
|
@ -69,6 +69,10 @@ func (q *SearchQuery) ResourceOwnerFilter(resourceOwner string) *SearchQuery {
|
|||||||
return q.setFilter(NewFilter(Field_ResourceOwner, resourceOwner, Operation_Equals))
|
return q.setFilter(NewFilter(Field_ResourceOwner, resourceOwner, Operation_Equals))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *SearchQuery) TenantFilter(tenant string) *SearchQuery {
|
||||||
|
return q.setFilter(NewFilter(Field_Tenant, tenant, Operation_Equals))
|
||||||
|
}
|
||||||
|
|
||||||
func (q *SearchQuery) CreationDateNewerFilter(time time.Time) *SearchQuery {
|
func (q *SearchQuery) CreationDateNewerFilter(time time.Time) *SearchQuery {
|
||||||
return q.setFilter(NewFilter(Field_CreationDate, time, Operation_Greater))
|
return q.setFilter(NewFilter(Field_CreationDate, time, Operation_Greater))
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ func ReduceEvent(handler Handler, event *models.Event) {
|
|||||||
}()
|
}()
|
||||||
currentSequence, err := handler.CurrentSequence()
|
currentSequence, err := handler.CurrentSequence()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("HANDL-BmpkC").WithError(err).Warn("unable to get current sequence")
|
logging.New().WithError(err).Warn("unable to get current sequence")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ func ReduceEvent(handler Handler, event *models.Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if unprocessedEvent.Sequence < currentSequence {
|
if unprocessedEvent.Sequence < currentSequence {
|
||||||
logging.LogWithFields("QUERY-DOYVN",
|
logging.WithFields(
|
||||||
"unprocessed", unprocessedEvent.Sequence,
|
"unprocessed", unprocessedEvent.Sequence,
|
||||||
"current", currentSequence,
|
"current", currentSequence,
|
||||||
"view", handler.ViewModel()).
|
"view", handler.ViewModel()).
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package spooler
|
package spooler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
|
||||||
|
v1 "github.com/caos/zitadel/internal/eventstore/v1"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||||
"github.com/caos/zitadel/internal/id"
|
"github.com/caos/zitadel/internal/id"
|
||||||
)
|
)
|
||||||
@ -21,7 +22,7 @@ func (c *Config) New() *Spooler {
|
|||||||
lockID, err := os.Hostname()
|
lockID, err := os.Hostname()
|
||||||
if err != nil || lockID == "" {
|
if err != nil || lockID == "" {
|
||||||
lockID, err = id.SonyFlakeGenerator.Next()
|
lockID, err = id.SonyFlakeGenerator.Next()
|
||||||
logging.Log("SPOOL-bdO56").OnError(err).Panic("unable to generate lockID")
|
logging.OnError(err).Panic("unable to generate lockID")
|
||||||
}
|
}
|
||||||
|
|
||||||
//shuffle the handlers for better balance when running multiple pods
|
//shuffle the handlers for better balance when running multiple pods
|
||||||
|
@ -2,15 +2,14 @@ package spooler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
|
||||||
|
v1 "github.com/caos/zitadel/internal/eventstore/v1"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||||
"github.com/caos/zitadel/internal/telemetry/tracing"
|
"github.com/caos/zitadel/internal/telemetry/tracing"
|
||||||
@ -38,7 +37,7 @@ type spooledHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spooler) Start() {
|
func (s *Spooler) Start() {
|
||||||
defer logging.LogWithFields("SPOOL-N0V1g", "lockerID", s.lockID, "workers", s.workers).Info("spooler started")
|
defer logging.WithFields("lockerID", s.lockID, "workers", s.workers).Info("spooler started")
|
||||||
if s.workers < 1 {
|
if s.workers < 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -116,7 +115,7 @@ func (s *spooledHandler) process(ctx context.Context, events []*models.Event, wo
|
|||||||
for i, event := range events {
|
for i, event := range events {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
logging.LogWithFields("SPOOL-FTKwH", "view", s.ViewModel(), "worker", workerID, "traceID", tracing.TraceIDFromCtx(ctx)).Debug("context canceled")
|
logging.WithFields("view", s.ViewModel(), "worker", workerID, "traceID", tracing.TraceIDFromCtx(ctx)).Debug("context canceled")
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
if err := s.Reduce(event); err != nil {
|
if err := s.Reduce(event); err != nil {
|
||||||
@ -130,7 +129,7 @@ func (s *spooledHandler) process(ctx context.Context, events []*models.Event, wo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := s.OnSuccess()
|
err := s.OnSuccess()
|
||||||
logging.LogWithFields("SPOOL-49ods", "view", s.ViewModel(), "worker", workerID, "traceID", tracing.TraceIDFromCtx(ctx)).OnError(err).Warn("could not process on success func")
|
logging.WithFields("view", s.ViewModel(), "worker", workerID, "traceID", tracing.TraceIDFromCtx(ctx)).OnError(err).Warn("could not process on success func")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +140,7 @@ func (s *spooledHandler) query(ctx context.Context) ([]*models.Event, error) {
|
|||||||
}
|
}
|
||||||
factory := models.FactoryFromSearchQuery(query)
|
factory := models.FactoryFromSearchQuery(query)
|
||||||
sequence, err := s.eventstore.LatestSequence(ctx, factory)
|
sequence, err := s.eventstore.LatestSequence(ctx, factory)
|
||||||
logging.Log("SPOOL-7SciK").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to query latest sequence")
|
logging.OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to query latest sequence")
|
||||||
var processedSequence uint64
|
var processedSequence uint64
|
||||||
for _, filter := range query.Filters {
|
for _, filter := range query.Filters {
|
||||||
if filter.GetField() == models.Field_LatestSequence {
|
if filter.GetField() == models.Field_LatestSequence {
|
||||||
|
@ -10,6 +10,7 @@ type WriteModel struct {
|
|||||||
ProcessedSequence uint64 `json:"-"`
|
ProcessedSequence uint64 `json:"-"`
|
||||||
Events []Event `json:"-"`
|
Events []Event `json:"-"`
|
||||||
ResourceOwner string `json:"-"`
|
ResourceOwner string `json:"-"`
|
||||||
|
Tenant string `json:"-"`
|
||||||
ChangeDate time.Time `json:"-"`
|
ChangeDate time.Time `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +33,9 @@ func (wm *WriteModel) Reduce() error {
|
|||||||
if wm.ResourceOwner == "" {
|
if wm.ResourceOwner == "" {
|
||||||
wm.ResourceOwner = wm.Events[0].Aggregate().ResourceOwner
|
wm.ResourceOwner = wm.Events[0].Aggregate().ResourceOwner
|
||||||
}
|
}
|
||||||
|
if wm.Tenant == "" {
|
||||||
|
wm.Tenant = wm.Events[0].Aggregate().Tenant
|
||||||
|
}
|
||||||
|
|
||||||
wm.ProcessedSequence = wm.Events[len(wm.Events)-1].Sequence()
|
wm.ProcessedSequence = wm.Events[len(wm.Events)-1].Sequence()
|
||||||
wm.ChangeDate = wm.Events[len(wm.Events)-1].CreationDate()
|
wm.ChangeDate = wm.Events[len(wm.Events)-1].CreationDate()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user