diff --git a/cmd/admin/initialise/verify_grant.go b/cmd/admin/initialise/verify_grant.go index d2d236d0a3..af65284eff 100644 --- a/cmd/admin/initialise/verify_grant.go +++ b/cmd/admin/initialise/verify_grant.go @@ -28,7 +28,7 @@ Prereqesits: Run: func(cmd *cobra.Command, args []string) { config := MustNewConfig(viper.New()) - err := initialise(config, VerifyGrant(config.Database.Database, config.Database.User.Username)) + err := initialise(config, VerifyGrant(config.Database.Database, config.Database.Username)) logging.OnError(err).Fatal("unable to set grant") }, } @@ -36,7 +36,7 @@ Prereqesits: func VerifyGrant(database, username string) func(*sql.DB) error { return func(db *sql.DB) error { - logging.WithFields("user", username).Info("verify grant") + logging.WithFields("user", username, "database", database).Info("verify grant") return verify(db, exists(fmt.Sprintf(searchGrant, database), username), exec(fmt.Sprintf(grantStmt, database, username)), diff --git a/cmd/admin/setup/setup.go b/cmd/admin/setup/setup.go index 18c931dfa9..5745ca8b34 100644 --- a/cmd/admin/setup/setup.go +++ b/cmd/admin/setup/setup.go @@ -3,6 +3,7 @@ package setup import ( "context" _ "embed" + "strings" "github.com/caos/logging" "github.com/spf13/cobra" @@ -49,6 +50,12 @@ func Setup(config *Config, steps *Steps, masterKey string) { steps.s1ProjectionTable = &ProjectionTable{dbClient: dbClient} steps.s2AssetsTable = &AssetTable{dbClient: dbClient} + + steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address = strings.TrimSpace(steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address) + if steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address == "" { + steps.S3DefaultInstance.InstanceSetup.Org.Human.Email.Address = "admin@" + config.ExternalDomain + } + steps.S3DefaultInstance.es = eventstoreClient steps.S3DefaultInstance.db = dbClient steps.S3DefaultInstance.defaults = config.SystemDefaults diff --git a/cmd/admin/setup/steps.yaml b/cmd/admin/setup/steps.yaml index 5089198b5e..0e98f1444a 100644 --- a/cmd/admin/setup/steps.yaml +++ b/cmd/admin/setup/steps.yaml @@ -9,7 +9,7 @@ S3DefaultInstance: NickName: DisplayName: Email: - Address: admin@zitadel.ch + Address: #autogenerated if empty. uses domain from config and prefixes admin@. for example: admin@domain.tdl Verified: true PreferredLanguage: Gender: diff --git a/internal/command/instance.go b/internal/command/instance.go index 44ec11a98d..ae29a28f6c 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -162,6 +162,12 @@ func (c *commandNew) SetUpInstance(ctx context.Context, setup *InstanceSetup) (* if err != nil { return nil, err } + + if err = c.es.NewInstance(ctx, instanceID); err != nil { + return nil, err + } + + ctx = authz.SetCtxData(authz.WithInstanceID(ctx, instanceID), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID}) requestedDomain := authz.GetInstance(ctx).RequestedDomain() ctx = authz.SetCtxData(authz.WithRequestedDomain(authz.WithInstanceID(ctx, instanceID), requestedDomain), authz.CtxData{OrgID: instanceID, ResourceOwner: instanceID}) diff --git a/internal/eventstore/eventstore.go b/internal/eventstore/eventstore.go index 47b4778ea6..29d1ffa1f9 100644 --- a/internal/eventstore/eventstore.go +++ b/internal/eventstore/eventstore.go @@ -59,6 +59,10 @@ func (es *Eventstore) Push(ctx context.Context, cmds ...Command) ([]Event, error return eventReaders, nil } +func (es *Eventstore) NewInstance(ctx context.Context, instanceID string) error { + return es.repo.CreateInstance(ctx, instanceID) +} + func commandsToRepository(instanceID string, cmds []Command) (events []*repository.Event, constraints []*repository.UniqueConstraint, err error) { events = make([]*repository.Event, len(cmds)) for i, cmd := range cmds { @@ -245,7 +249,3 @@ func uniqueConstraintActionToRepository(action UniqueConstraintAction) repositor return repository.UniqueConstraintAdd } } - -func (es *Eventstore) Step20(ctx context.Context, latestSequence uint64) error { - return es.repo.Step20(ctx, latestSequence) -} diff --git a/internal/eventstore/eventstore_test.go b/internal/eventstore/eventstore_test.go index 0e2738955a..44a870316a 100644 --- a/internal/eventstore/eventstore_test.go +++ b/internal/eventstore/eventstore_test.go @@ -698,6 +698,10 @@ func (repo *testRepo) Health(ctx context.Context) error { return nil } +func (repo *testRepo) CreateInstance(ctx context.Context, instance string) error { + return nil +} + func (repo *testRepo) Step20(context.Context, uint64) error { return nil } func (repo *testRepo) Push(ctx context.Context, events []*repository.Event, uniqueConstraints ...*repository.UniqueConstraint) error { diff --git a/internal/eventstore/repository/mock/repository.mock.go b/internal/eventstore/repository/mock/repository.mock.go index 419186eef0..e0d64822a2 100644 --- a/internal/eventstore/repository/mock/repository.mock.go +++ b/internal/eventstore/repository/mock/repository.mock.go @@ -35,6 +35,20 @@ func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder { return m.recorder } +// CreateInstance mocks base method. +func (m *MockRepository) CreateInstance(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateInstance", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateInstance indicates an expected call of CreateInstance. +func (mr *MockRepositoryMockRecorder) CreateInstance(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateInstance", reflect.TypeOf((*MockRepository)(nil).CreateInstance), arg0, arg1) +} + // Filter mocks base method. func (m *MockRepository) Filter(arg0 context.Context, arg1 *repository.SearchQuery) ([]*repository.Event, error) { m.ctrl.T.Helper() @@ -97,17 +111,3 @@ func (mr *MockRepositoryMockRecorder) Push(arg0, arg1 interface{}, arg2 ...inter varargs := append([]interface{}{arg0, arg1}, arg2...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Push", reflect.TypeOf((*MockRepository)(nil).Push), varargs...) } - -// Step20 mocks base method. -func (m *MockRepository) Step20(arg0 context.Context, arg1 uint64) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Step20", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// Step20 indicates an expected call of Step20. -func (mr *MockRepositoryMockRecorder) Step20(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Step20", reflect.TypeOf((*MockRepository)(nil).Step20), arg0, arg1) -} diff --git a/internal/eventstore/repository/repository.go b/internal/eventstore/repository/repository.go index da591b3bbf..afbb4d2fff 100644 --- a/internal/eventstore/repository/repository.go +++ b/internal/eventstore/repository/repository.go @@ -16,6 +16,6 @@ type Repository interface { Filter(ctx context.Context, searchQuery *SearchQuery) (events []*Event, err error) //LatestSequence returns the latests sequence found by the the search query LatestSequence(ctx context.Context, queryFactory *SearchQuery) (uint64, error) - - Step20(ctx context.Context, latestSequence uint64) error + //CreateInstance creates a new sequence for the given instance + CreateInstance(ctx context.Context, instanceID string) error } diff --git a/internal/eventstore/repository/sql/crdb.go b/internal/eventstore/repository/sql/crdb.go index cf5d53443e..9f5b6c318a 100644 --- a/internal/eventstore/repository/sql/crdb.go +++ b/internal/eventstore/repository/sql/crdb.go @@ -153,6 +153,25 @@ func (db *CRDB) Push(ctx context.Context, events []*repository.Event, uniqueCons return err } +var instanceRegexp = regexp.MustCompile(`eventstore\.i_[0-9a-zA-Z]{1,}_seq`) + +func (db *CRDB) CreateInstance(ctx context.Context, instanceID string) error { + row := db.client.QueryRowContext(ctx, "SELECT CONCAT('eventstore.i_', $1, '_seq')", instanceID) + if row.Err() != nil { + return caos_errs.ThrowInvalidArgument(row.Err(), "SQL-7gtFA", "Errors.InvalidArgument") + } + var sequenceName string + if err := row.Scan(&sequenceName); err != nil || !instanceRegexp.MatchString(sequenceName) { + return caos_errs.ThrowInvalidArgument(err, "SQL-7gtFA", "Errors.InvalidArgument") + } + + if _, err := db.client.ExecContext(ctx, "CREATE SEQUENCE "+sequenceName); err != nil { + return caos_errs.ThrowInternal(err, "SQL-7gtFA", "Errors.Internal") + } + + return nil +} + // handleUniqueConstraints adds or removes unique constraints func (db *CRDB) handleUniqueConstraints(ctx context.Context, tx *sql.Tx, uniqueConstraints ...*repository.UniqueConstraint) (err error) { if len(uniqueConstraints) == 0 || (len(uniqueConstraints) == 1 && uniqueConstraints[0] == nil) { diff --git a/internal/eventstore/repository/sql/crdb_test.go b/internal/eventstore/repository/sql/crdb_test.go index 12ba3255e7..5cc4700b2b 100644 --- a/internal/eventstore/repository/sql/crdb_test.go +++ b/internal/eventstore/repository/sql/crdb_test.go @@ -561,6 +561,84 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) { } } +func TestCRDB_CreateInstance(t *testing.T) { + type args struct { + instanceID string + } + type res struct { + wantErr bool + exists bool + } + tests := []struct { + name string + args args + res res + }{ + { + name: "no number", + args: args{ + instanceID: "asdf;use defaultdb;DROP DATABASE zitadel;--", + }, + res: res{ + wantErr: true, + exists: false, + }, + }, + { + name: "no instance id", + args: args{ + instanceID: "", + }, + res: res{ + wantErr: true, + exists: false, + }, + }, + { + name: "correct number", + args: args{ + instanceID: "1235", + }, + res: res{ + wantErr: false, + exists: true, + }, + }, + { + name: "correct text", + args: args{ + instanceID: "system", + }, + res: res{ + wantErr: false, + exists: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db := &CRDB{ + client: testCRDBClient, + } + + if err := db.CreateInstance(context.Background(), tt.args.instanceID); (err != nil) != tt.res.wantErr { + t.Errorf("CRDB.CreateInstance() error = %v, wantErr %v", err, tt.res.wantErr) + } + + sequenceRow := testCRDBClient.QueryRow("SELECT EXISTS(SELECT 1 FROM [SHOW SEQUENCES FROM eventstore] WHERE sequence_name like $1)", "i_"+tt.args.instanceID+"%") + var exists bool + err := sequenceRow.Scan(&exists) + if err != nil { + t.Error("unable to query inserted rows: ", err) + return + } + if exists != tt.res.exists { + t.Errorf("expected exists %v got %v", tt.res.exists, exists) + } + }) + } +} + func TestCRDB_Push_Parallel(t *testing.T) { type args struct { events [][]*repository.Event diff --git a/internal/eventstore/repository/sql/setup.go b/internal/eventstore/repository/sql/setup.go deleted file mode 100644 index b73cb0dc4a..0000000000 --- a/internal/eventstore/repository/sql/setup.go +++ /dev/null @@ -1,58 +0,0 @@ -package sql - -import ( - "context" - - "github.com/caos/logging" - repo "github.com/caos/zitadel/internal/eventstore/repository" -) - -func (db *CRDB) Step20(ctx context.Context, latestSequence uint64) error { - currentSequence := uint64(0) - limit := uint64(500) - previousSequences := make(map[repo.AggregateType]Sequence) - for currentSequence < latestSequence { - events, err := db.Filter(ctx, &repo.SearchQuery{ - Columns: repo.ColumnsEvent, - Limit: limit, - Filters: [][]*repo.Filter{ - { - &repo.Filter{ - Field: repo.FieldSequence, - Operation: repo.OperationGreater, - Value: currentSequence, - }, - }, - }, - }) - if err != nil { - return err - } - - tx, err := db.client.Begin() - if err != nil { - return err - } - - for _, event := range events { - if _, err := tx.Exec("SAVEPOINT event_update"); err != nil { - return err - } - seq := Sequence(previousSequences[event.AggregateType]) - if _, err = tx.Exec("UPDATE eventstore.events SET previous_aggregate_type_sequence = $1 WHERE event_sequence = $2", &seq, event.Sequence); err != nil { - return err - } - if _, err = tx.Exec("RELEASE SAVEPOINT event_update"); err != nil { - return err - } - previousSequences[event.AggregateType] = Sequence(event.Sequence) - currentSequence = event.Sequence - } - - if err = tx.Commit(); err != nil { - return err - } - logging.WithFields("currentSeq", currentSequence, "events", len(events)).Info("events updated") - } - return nil -}