diff --git a/apps/accounts/internal/domain/invitations.go b/apps/accounts/internal/domain/invitations.go index 0fc01fa5f..16647fec0 100644 --- a/apps/accounts/internal/domain/invitations.go +++ b/apps/accounts/internal/domain/invitations.go @@ -2,6 +2,7 @@ package domain import ( "context" + "github.com/kloudlite/api/apps/accounts/internal/entities" fc "github.com/kloudlite/api/apps/accounts/internal/entities/field-constants" iamT "github.com/kloudlite/api/apps/iam/types" @@ -180,12 +181,12 @@ func (d *domain) AcceptInvitation(ctx UserContext, accountName string, inviteTok return false, errors.Newf("invitation already accepted or rejected, won't process further") } - inv.Accepted = fn.New(true) - if _, err := d.invitationRepo.UpdateById(ctx, inv.Id, inv); err != nil { + if err := d.addMembership(ctx, accountName, ctx.UserId, inv.UserRole); err != nil { return false, errors.NewE(err) } - if err := d.addMembership(ctx, accountName, ctx.UserId, inv.UserRole); err != nil { + // INFO: invitation accepted, removing invite + if err := d.invitationRepo.DeleteById(ctx, inv.Id); err != nil { return false, errors.NewE(err) } diff --git a/apps/accounts/internal/domain/memberships.go b/apps/accounts/internal/domain/memberships.go index cbc0b0ba2..47d782bf5 100644 --- a/apps/accounts/internal/domain/memberships.go +++ b/apps/accounts/internal/domain/memberships.go @@ -2,9 +2,10 @@ package domain import ( "context" - "github.com/kloudlite/api/apps/accounts/internal/entities" "strings" + "github.com/kloudlite/api/apps/accounts/internal/entities" + iamT "github.com/kloudlite/api/apps/iam/types" "github.com/kloudlite/api/grpc-interfaces/kloudlite.io/rpc/iam" "github.com/kloudlite/api/pkg/errors" @@ -69,7 +70,6 @@ func (d *domain) UpdateAccountMembership(ctx UserContext, accountName string, me ResourceRef: iamT.NewResourceRef(accountName, iamT.ResourceAccount, accountName), Role: string(role), }) - if err != nil { return false, errors.NewE(err) } diff --git a/apps/accounts/main.go b/apps/accounts/main.go index 9014f4bba..cc1a19068 100644 --- a/apps/accounts/main.go +++ b/apps/accounts/main.go @@ -3,7 +3,6 @@ package main import ( "context" "flag" - "log/slog" "os" "time" @@ -20,37 +19,42 @@ import ( ) func main() { + start := time.Now() + var isDev bool flag.BoolVar(&isDev, "dev", false, "--dev") + + var debug bool + flag.BoolVar(&debug, "debug", false, "--debug") + flag.Parse() - logger, err := logging.New(&logging.Options{Name: "accounts", Dev: isDev}) - if err != nil { - panic(err) + if isDev { + debug = true } + logger := logging.NewSlogLogger(logging.SlogOptions{ + ShowCaller: true, + ShowDebugLogs: debug, + SetAsDefaultLogger: true, + }) + app := fx.New( fx.NopLogger, - fx.Provide(func() logging.Logger { - return logger + fx.Provide(func() (logging.Logger, error) { + return logging.New(&logging.Options{Name: "accounts-api", Dev: isDev, ShowDebugLog: debug}) }), - fx.Provide(func() *slog.Logger { - return logging.NewSlogLogger(logging.SlogOptions{ - ShowCaller: true, - ShowDebugLogs: isDev, - SetAsDefaultLogger: true, - }) - }), + fx.Supply(logger), fx.Provide(func() (*env.Env, error) { - if e, err := env.LoadEnv(); err != nil { + e, err := env.LoadEnv() + if err != nil { return nil, errors.NewE(err) - } else { - e.IsDev = isDev - return e, nil } + e.IsDev = isDev + return e, nil }), fx.Provide(func(e *env.Env) (*rest.Config, error) { @@ -79,10 +83,10 @@ func main() { defer cancelFunc() if err := app.Start(ctx); err != nil { - logger.Errorf(err, "error starting accounts app") + logger.Error("failed to start accounts api, got", "err", err) os.Exit(1) } - common.PrintReadyBanner() + common.PrintReadyBanner2(time.Since(start)) <-app.Done() } diff --git a/apps/console/internal/domain/environment.go b/apps/console/internal/domain/environment.go index 741b6a539..fa9287848 100644 --- a/apps/console/internal/domain/environment.go +++ b/apps/console/internal/domain/environment.go @@ -666,13 +666,17 @@ func (d *domain) OnEnvironmentDeleteMessage(ctx ConsoleContext, env entities.Env return errors.NewE(err) } - if err := d.environmentRepo.DeleteOne( - ctx, - repos.Filter{ - fields.AccountName: ctx.AccountName, - fields.MetadataName: env.Name, - }, - ); err != nil { + if err := d.environmentRepo.DeleteOne(ctx, repos.Filter{ + fields.AccountName: ctx.AccountName, + fields.MetadataName: env.Name, + }); err != nil { + return errors.NewE(err) + } + + if err := d.resourceMappingRepo.DeleteMany(ctx, repos.Filter{ + fc.ResourceMappingResourceHeirarchy: entities.ResourceHeirarchyEnvironment, + fc.EnvironmentName: env.Name, + }); err != nil { return errors.NewE(err) } @@ -701,16 +705,9 @@ func (d *domain) OnEnvironmentUpdateMessage(ctx ConsoleContext, env entities.Env return d.resyncK8sResource(ctx, xenv.Name, xenv.SyncStatus.Action, &xenv.Environment, xenv.RecordVersion) } - uenv, err := d.environmentRepo.PatchById( - ctx, - xenv.Id, - common.PatchForSyncFromAgent( - &env, - recordVersion, - status, - common.PatchOpts{ - MessageTimestamp: opts.MessageTimestamp, - })) + uenv, err := d.environmentRepo.PatchById(ctx, xenv.Id, common.PatchForSyncFromAgent( + &env, recordVersion, status, common.PatchOpts{MessageTimestamp: opts.MessageTimestamp}), + ) if err != nil { return err } diff --git a/apps/iam/internal/app/grpc-server.go b/apps/iam/internal/app/grpc-server.go index 8858443d6..72585d3a3 100644 --- a/apps/iam/internal/app/grpc-server.go +++ b/apps/iam/internal/app/grpc-server.go @@ -11,7 +11,6 @@ import ( t "github.com/kloudlite/api/apps/iam/types" "github.com/kloudlite/api/grpc-interfaces/kloudlite.io/rpc/iam" "github.com/kloudlite/api/pkg/errors" - fn "github.com/kloudlite/api/pkg/functions" "github.com/kloudlite/api/pkg/logging" "github.com/kloudlite/api/pkg/repos" ) @@ -52,6 +51,8 @@ func (s *GrpcService) UpdateMembership(ctx context.Context, in *iam.UpdateMember }, nil } +var ErrRoleBindingNotFound error = fmt.Errorf("role binding not found") + func (s *GrpcService) findRoleBinding(ctx context.Context, userId repos.ID, resourceRef string) (*entities.RoleBinding, error) { rb, err := s.rbRepo.FindOne( ctx, repos.Filter{ @@ -63,7 +64,7 @@ func (s *GrpcService) findRoleBinding(ctx context.Context, userId repos.ID, reso return nil, errors.NewE(err) } if rb == nil { - return nil, errors.Newf("role binding for (userId=%s, ResourceRef=%s) not found", userId, resourceRef) + return nil, ErrRoleBindingNotFound } return rb, nil } @@ -121,45 +122,99 @@ func (s *GrpcService) Can(ctx context.Context, in *iam.CanIn) (*iam.CanOut, erro return &iam.CanOut{Status: false}, nil } - var hasAccountMemberRole bool - - canFilter := repos.Filter{ - "resource_ref": map[string]any{"$in": in.ResourceRefs}, - "user_id": in.UserId, + arb := make([]any, len(rb)) + for i := range rb { + arb = append(arb, rb[i]) } - for i := range rb { - if rb[i] == t.RoleAccountMember { - hasAccountMemberRole = true - - rr := make([]map[string]any, 0, len(in.ResourceRefs)) - - for i := range in.ResourceRefs { - accountName, _, _, err := t.ParseResourceRef(in.ResourceRefs[i]) - if err != nil { - return nil, err - } - - if strings.TrimSpace(accountName) == "" { - return nil, fmt.Errorf("accountName must be provided") - } - - nf := s.rbRepo.MergeMatchFilters(repos.Filter{}, map[string]repos.MatchFilter{ - "resource_ref": { - MatchType: repos.MatchTypeRegex, - Regex: fn.New(t.NewResourceRef(accountName, "*", "*")), - }, - }) - rr = append(rr, map[string]any{"resource_ref": nf["resource_ref"]}) - } - - delete(canFilter, "resource_ref") - canFilter["$or"] = rr + accountName := "" + for i := range in.ResourceRefs { + acc, _, _, err := t.ParseResourceRef(in.ResourceRefs[i]) + if err != nil { + return nil, err } + accountName = acc } - rbs, err := s.rbRepo.Find( - ctx, repos.Query{Filter: canFilter}, + // var hasAccountMemberRole bool + + // resourceFilter := repos.Filter{ + // "resource_ref": map[string]any{"$in": in.ResourceRefs}, + // "user_id": in.UserId, + // } + + // resourceFilter = s.rbRepo.MergeMatchFilters(resourceFilter, map[string]repos.MatchFilter{ + // "role": { + // MatchType: repos.MatchTypeArray, + // Array: arb, + // }, + // }) + + accountLevelFilter := s.rbRepo.MergeMatchFilters(repos.Filter{}, map[string]repos.MatchFilter{ + "user_id": { + MatchType: repos.MatchTypeExact, + Exact: in.UserId, + }, + "resource_ref": { + MatchType: repos.MatchTypeExact, + Exact: fmt.Sprintf("%s/account/%s", accountName, accountName), + }, + "role": { + MatchType: repos.MatchTypeArray, + Array: []any{t.RoleAccountOwner, t.RoleAccountAdmin, t.RoleAccountMember}, + }, + }) + + // for i := range rb { + // if rb[i] == t.RoleAccountMember { + // hasAccountMemberRole = true + // + // rr := make([]map[string]any, 0, len(in.ResourceRefs)) + // + // for i := range in.ResourceRefs { + // accountName, _, _, err := t.ParseResourceRef(in.ResourceRefs[i]) + // if err != nil { + // return nil, err + // } + // + // if strings.TrimSpace(accountName) == "" { + // return nil, fmt.Errorf("accountName must be provided") + // } + // + // nf := s.rbRepo.MergeMatchFilters(repos.Filter{}, map[string]repos.MatchFilter{ + // "resource_ref": { + // MatchType: repos.MatchTypeRegex, + // // FIXME: HERE + // Regex: fn.New(t.NewResourceRef(accountName, "*", "*")), + // }, + // }) + // rr = append(rr, map[string]any{ + // "resource_ref": nf["resource_ref"], + // }) + // } + // + // // FIXME: error HERE + // delete(canFilter, "resource_ref") + // canFilter["$or"] = rr + // } + // } + + // accountMemberFilter := repos.Filter{ + // "resource_ref": + // } + // + // resourceFilter = s.rbRepo.MergeMatchFilters(resourceFilter, map[string]repos.MatchFilter{ + // "role": { + // MatchType: repos.MatchTypeArray, + // Array: []any{t.RoleAccountOwner, t.RoleAccountAdmin, t.RoleAccountMember}, + // }, + // }) + + rbs, err := s.rbRepo.Find(ctx, repos.Query{Filter: repos.Filter{ + "$and": []map[string]any{ + accountLevelFilter, + }, + }}, ) if err != nil { return nil, errors.NewEf(err, "could not find rolebindings for (resourceRefs=%s)", strings.Join(in.ResourceRefs, ",")) @@ -169,18 +224,22 @@ func (s *GrpcService) Can(ctx context.Context, in *iam.CanIn) (*iam.CanOut, erro return nil, errors.Newf("no rolebinding found for (userId=%s, resourceRefs=%s)", in.UserId, strings.Join(in.ResourceRefs, ",")) } - if hasAccountMemberRole && len(rbs) > 0 { + if len(rbs) > 0 { return &iam.CanOut{Status: true}, nil } - for i := range rbs { - // 2nd loop, but very small length (always < #roles), so it's not exactly O(n^2), much like XO(n) - for _, role := range s.roleBindingMap[t.Action(in.Action)] { - if role == rbs[i].Role { - return &iam.CanOut{Status: true}, nil - } - } - } + // if hasAccountMemberRole && len(rbs) > 0 { + // return &iam.CanOut{Status: true}, nil + // } + // + // for i := range rbs { + // // 2nd loop, but very small length (always < #roles), so it's not exactly O(n^2), much like XO(n) + // for _, role := range s.roleBindingMap[t.Action(in.Action)] { + // if role == rbs[i].Role { + // return &iam.CanOut{Status: true}, nil + // } + // } + // } return &iam.CanOut{Status: false}, nil } @@ -236,6 +295,10 @@ func (s *GrpcService) RemoveMembership(ctx context.Context, in *iam.RemoveMember rb, err := s.findRoleBinding(ctx, repos.ID(in.UserId), in.ResourceRef) if err != nil { + if errors.Is(err, ErrRoleBindingNotFound) { + s.logger.WithKV("userID", in.UserId, "resourceRef", in.ResourceRef).Infof("role binding might already have been deleted") + return &iam.RemoveMembershipOut{Result: true}, nil + } return nil, errors.NewE(err) } diff --git a/apps/iam/internal/app/main.go b/apps/iam/internal/app/main.go index eb9a2105a..827a4b061 100644 --- a/apps/iam/internal/app/main.go +++ b/apps/iam/internal/app/main.go @@ -2,10 +2,11 @@ package app import ( "encoding/json" + "os" + "github.com/kloudlite/api/apps/iam/internal/entities" "github.com/kloudlite/api/pkg/errors" "github.com/kloudlite/api/pkg/logging" - "os" "github.com/kloudlite/api/apps/iam/internal/env" "github.com/kloudlite/api/grpc-interfaces/kloudlite.io/rpc/iam" diff --git a/apps/iam/internal/env/env.go b/apps/iam/internal/env/env.go index b4be0651e..4c6271b9c 100644 --- a/apps/iam/internal/env/env.go +++ b/apps/iam/internal/env/env.go @@ -11,6 +11,8 @@ type Env struct { MongoDbName string `env:"MONGO_DB_NAME" required:"true"` ActionRoleMapFile string `env:"ACTION_ROLE_MAP_FILE" required:"false"` + + ShowGRPCLogs bool `env:"SHOW_GRPC_LOGS" default:"false"` } func LoadEnv() (*Env, error) { diff --git a/apps/iam/internal/framework/main.go b/apps/iam/internal/framework/main.go index cb60565e3..5a3460964 100644 --- a/apps/iam/internal/framework/main.go +++ b/apps/iam/internal/framework/main.go @@ -33,8 +33,8 @@ var Module fx.Option = fx.Module( }), repos.NewMongoClientFx[*fm](), - fx.Provide(func(logger *slog.Logger) (app.IAMGrpcServer, error) { - return grpc.NewGrpcServer(grpc.ServerOpts{Logger: logger}) + fx.Provide(func(logger *slog.Logger, ev *env.Env) (app.IAMGrpcServer, error) { + return grpc.NewGrpcServer(grpc.ServerOpts{Logger: logger, ShowLogs: ev.ShowGRPCLogs}) }), app.Module, diff --git a/apps/iam/main.go b/apps/iam/main.go index 564607137..275c9209b 100644 --- a/apps/iam/main.go +++ b/apps/iam/main.go @@ -37,7 +37,12 @@ func main() { }), fx.Provide(func() (*env.Env, error) { - return env.LoadEnv() + e, err := env.LoadEnv() + if err != nil { + return nil, err + } + e.ShowGRPCLogs = debug + return e, nil }), framework.Module, diff --git a/apps/infra/internal/domain/byok-clusters.go b/apps/infra/internal/domain/byok-clusters.go index fe67c7df0..ec1327229 100644 --- a/apps/infra/internal/domain/byok-clusters.go +++ b/apps/infra/internal/domain/byok-clusters.go @@ -203,6 +203,11 @@ func (d *domain) GetBYOKClusterSetupInstructions(ctx InfraContext, name string, return nil, err } + gvpnConn, err := d.findGlobalVPNConnection(ctx, ctx.AccountName, cluster.Name, cluster.GlobalVPN) + if err != nil { + return nil, err + } + if onlyHelmValues { b, err := json.Marshal(map[string]any{ "crds-url": fmt.Sprintf("https://github.com/kloudlite/helm-charts/releases/download/%s/crds-all.yml", d.env.KloudliteRelease), @@ -217,6 +222,11 @@ func (d *domain) GetBYOKClusterSetupInstructions(ctx InfraContext, name string, "messageOfficeGRPCAddr": d.env.MessageOfficeExternalGrpcAddr, "kloudliteDNSSuffix": fmt.Sprintf("%s.%s", ctx.AccountName, d.env.KloudliteDNSSuffix), }, + + "gateway": map[string]any{ + "IP": gvpnConn.Spec.GlobalIP, + "clusterCIDR": gvpnConn.Spec.ClusterCIDR, + }, }) if err != nil { return nil, err diff --git a/apps/infra/internal/domain/clusters.go b/apps/infra/internal/domain/clusters.go index 82bd87dbe..b68c3493d 100644 --- a/apps/infra/internal/domain/clusters.go +++ b/apps/infra/internal/domain/clusters.go @@ -594,8 +594,18 @@ func (d *domain) syncKloudliteGatewayDevice(ctx InfraContext, gvpnName string) e } d.logger.Info("applying yaml", "yaml", string(deploymentBytes)) - if _, err := yc.ApplyYAML(ctx, deploymentBytes); err != nil { - return errors.NewE(err) + for i := 0; i < 5; i++ { + if _, err = yc.ApplyYAML(ctx, deploymentBytes); err != nil { + d.logger.Warn("syncing to kloudlite gateway device, failing with", "err", err, "retry.count", i, "retry.remaining", 5-i) + continue + // return errors.NewE(err) + } + err = nil + break + } + + if err != nil { + return err } return nil @@ -773,8 +783,18 @@ func (d *domain) syncKloudliteDeviceOnPlatform(ctx InfraContext, gvpnName string return err } - if err := d.k8sClient.ApplyYAML(ctx, deploymentBytes); err != nil { - return errors.NewE(err) + for i := 0; i < 5; i++ { + if err := d.k8sClient.ApplyYAML(ctx, deploymentBytes); err != nil { + d.logger.Warn("syncing to kloudlite platform device, failing with", "err", err, "retry.count", i, "retry.remaining", 5-i) + continue + // return errors.NewE(err) + } + err = nil + break + } + + if err != nil { + return err } return nil diff --git a/apps/infra/internal/domain/global-vpn-cluster-connection.go b/apps/infra/internal/domain/global-vpn-cluster-connection.go index 7b11f4ab0..a141e844c 100644 --- a/apps/infra/internal/domain/global-vpn-cluster-connection.go +++ b/apps/infra/internal/domain/global-vpn-cluster-connection.go @@ -369,6 +369,18 @@ func (d *domain) createGlobalVPNConnection(ctx InfraContext, gvpnConn entities.G return nil, err } + if err := d.resDispatcher.ApplyToTargetCluster(ctx, gvpnConn.DispatchAddr, &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: gvpnConn.Spec.TargetNamespace, + }, + }, gvpn.RecordVersion); err != nil { + return nil, err + } + if err := d.resDispatcher.ApplyToTargetCluster(ctx, gvpnConn.DispatchAddr, &corev1.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1",