snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
423
server/system/service/service.go
Normal file
423
server/system/service/service.go
Normal file
@@ -0,0 +1,423 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/bep/godartsass/v2"
|
||||
|
||||
automationService "github.com/cortezaproject/corteza/server/automation/service"
|
||||
discoveryService "github.com/cortezaproject/corteza/server/discovery/service"
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza/server/pkg/healthcheck"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/pkg/label"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/pkg/objstore"
|
||||
"github.com/cortezaproject/corteza/server/pkg/objstore/minio"
|
||||
"github.com/cortezaproject/corteza/server/pkg/objstore/plain"
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza/server/pkg/valuestore"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/system/automation"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
websocketSender interface {
|
||||
Send(kind string, payload interface{}, userIDs ...uint64) error
|
||||
}
|
||||
|
||||
Config struct {
|
||||
ActionLog options.ActionLogOpt
|
||||
Discovery options.DiscoveryOpt
|
||||
Storage options.ObjectStoreOpt
|
||||
DB options.DBOpt
|
||||
Template options.TemplateOpt
|
||||
Auth options.AuthOpt
|
||||
RBAC options.RbacOpt
|
||||
Limit options.LimitOpt
|
||||
Attachment options.AttachmentOpt
|
||||
Webapps options.WebappOpt
|
||||
}
|
||||
|
||||
eventDispatcher interface {
|
||||
WaitFor(ctx context.Context, ev eventbus.Event) (err error)
|
||||
Dispatch(ctx context.Context, ev eventbus.Event)
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultObjectStore objstore.Store
|
||||
|
||||
// DefaultStore is an interface to storage backend(s)
|
||||
// ng (next-gen) is a temporary prefix
|
||||
// so that we can differentiate between it and the file-only store
|
||||
DefaultStore store.Storer
|
||||
|
||||
DefaultLogger *zap.Logger
|
||||
|
||||
// DefaultSettings controls system's settings
|
||||
DefaultSettings *settings
|
||||
|
||||
DefaultStylesheet *stylesheet
|
||||
|
||||
// DefaultAccessControl Access control checking
|
||||
DefaultAccessControl *accessControl
|
||||
|
||||
DefaultAuthNotification AuthNotificationService
|
||||
|
||||
// CurrentSettings represents current system settings
|
||||
CurrentSettings = &types.AppSettings{}
|
||||
|
||||
DefaultActionlog actionlog.Recorder
|
||||
|
||||
DefaultSink *sink
|
||||
|
||||
DefaultAuth *auth
|
||||
DefaultAuthClient *authClient
|
||||
DefaultUser *user
|
||||
DefaultCredentials *credentials
|
||||
DefaultDalConnection *dalConnection
|
||||
DefaultDalSensitivityLevel *dalSensitivityLevel
|
||||
DefaultDalSchemaAlteration *dalSchemaAlteration
|
||||
DefaultRole *role
|
||||
DefaultUserGroup *userGroup
|
||||
DefaultApplication *application
|
||||
DefaultReminder ReminderService
|
||||
DefaultNotification NotificationService
|
||||
DefaultAttachment AttachmentService
|
||||
DefaultRenderer TemplateService
|
||||
DefaultResourceTranslation ResourceTranslationService
|
||||
DefaultQueue *queue
|
||||
DefaultApigwRoute *apigwRoute
|
||||
DefaultApigwFilter *apigwFilter
|
||||
DefaultApigwProfiler *apigwProfiler
|
||||
DefaultReport *report
|
||||
DefaultDataPrivacy *dataPrivacy
|
||||
DefaultSMTPChecker *smtpConfigurationChecker
|
||||
DefaultExpression *expression
|
||||
|
||||
DefaultStatistics *statistics
|
||||
|
||||
// wrapper around time.Now() that will aid service testing
|
||||
now = func() *time.Time {
|
||||
c := time.Now().Round(time.Second)
|
||||
return &c
|
||||
}
|
||||
|
||||
// wrapper around nextID that will aid service testing
|
||||
nextID = func() uint64 {
|
||||
return id.Next()
|
||||
}
|
||||
)
|
||||
|
||||
func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websocketSender, c Config) (err error) {
|
||||
var (
|
||||
hcd = healthcheck.Defaults()
|
||||
)
|
||||
|
||||
// we're doing conversion to avoid having
|
||||
// store interface exposed or generated inside app package
|
||||
DefaultStore = s
|
||||
|
||||
DefaultLogger = log.Named("service")
|
||||
|
||||
{
|
||||
tee := zap.NewNop()
|
||||
policy := actionlog.MakeProductionPolicy()
|
||||
|
||||
if !c.ActionLog.Enabled {
|
||||
policy = actionlog.MakeDisabledPolicy()
|
||||
} else if c.ActionLog.Debug {
|
||||
policy = actionlog.MakeDebugPolicy()
|
||||
tee = logger.MakeDebugLogger()
|
||||
}
|
||||
|
||||
DefaultActionlog = actionlog.NewService(DefaultStore, log, tee, policy)
|
||||
}
|
||||
|
||||
// Activity log for system resources
|
||||
{
|
||||
l := log
|
||||
if !c.Discovery.Debug {
|
||||
l = zap.NewNop()
|
||||
}
|
||||
|
||||
DefaultResourceActivity := discoveryService.ResourceActivity(l, c.Discovery, DefaultStore, eventbus.Service())
|
||||
err = DefaultResourceActivity.InitResourceActivityLog(ctx, []string{
|
||||
// (types.User{}).RbacResource(), // @todo user?? suppose to be system:user
|
||||
"system:user",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sassTranspiler := dartSassTranspiler(log)
|
||||
|
||||
DefaultAccessControl = AccessControl(s)
|
||||
|
||||
DefaultSettings = Settings(ctx, DefaultStore, DefaultLogger, DefaultAccessControl, DefaultActionlog, CurrentSettings, c.Webapps)
|
||||
DefaultStylesheet = Stylesheet(sassTranspiler, log)
|
||||
|
||||
DefaultDalConnection = Connection(ctx, dal.Service(), c.DB)
|
||||
|
||||
DefaultDalSensitivityLevel = SensitivityLevel(ctx, dal.Service())
|
||||
|
||||
DefaultDalSchemaAlteration = DalSchemaAlteration(dal.Service())
|
||||
|
||||
if DefaultObjectStore == nil {
|
||||
var (
|
||||
opt = c.Storage
|
||||
bucket string
|
||||
)
|
||||
const svcPath = "system"
|
||||
if opt.MinioEndpoint != "" {
|
||||
bucket = minio.GetBucket(opt.MinioBucket, svcPath)
|
||||
|
||||
DefaultObjectStore, err = minio.New(bucket, opt.MinioPathPrefix, svcPath, minio.Options{
|
||||
Endpoint: opt.MinioEndpoint,
|
||||
Secure: opt.MinioSecure,
|
||||
Strict: opt.MinioStrict,
|
||||
AccessKeyID: opt.MinioAccessKey,
|
||||
SecretAccessKey: opt.MinioSecretKey,
|
||||
|
||||
ServerSideEncryptKey: []byte(opt.MinioSSECKey),
|
||||
})
|
||||
|
||||
log.Info("initializing minio",
|
||||
zap.String("bucket", bucket),
|
||||
zap.String("endpoint", opt.MinioEndpoint),
|
||||
zap.Error(err))
|
||||
} else {
|
||||
path := opt.Path + "/" + svcPath
|
||||
DefaultObjectStore, err = plain.New(path)
|
||||
log.Info("initializing store",
|
||||
zap.String("path", path),
|
||||
zap.Error(err))
|
||||
}
|
||||
|
||||
hcd.Add(objstore.Healthcheck(DefaultObjectStore), "ObjectStore/System")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DefaultRenderer = Renderer(c.Template)
|
||||
DefaultResourceTranslation = ResourceTranslation()
|
||||
DefaultAuthNotification = AuthNotification(CurrentSettings, DefaultRenderer, c.Auth)
|
||||
DefaultAuth = Auth(AuthOptions{LimitUsers: c.Limit.SystemUsers})
|
||||
DefaultAuthClient = AuthClient(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service(), c.Auth)
|
||||
DefaultAttachment = Attachment(DefaultObjectStore, c.Attachment, DefaultLogger)
|
||||
DefaultUser = User(UserOptions{LimitUsers: c.Limit.SystemUsers})
|
||||
DefaultCredentials = Credentials()
|
||||
DefaultReport = Report(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service())
|
||||
DefaultRole = Role(rbac.Global())
|
||||
DefaultUserGroup = UserGroup(rbac.Global())
|
||||
DefaultApplication = Application(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service())
|
||||
DefaultReminder = Reminder(ctx, DefaultLogger.Named("reminder"), ws)
|
||||
DefaultNotification = Notification(ctx, DefaultLogger.Named("notification"), ws)
|
||||
DefaultSink = Sink()
|
||||
DefaultStatistics = Statistics()
|
||||
DefaultQueue = Queue()
|
||||
DefaultApigwRoute = Route()
|
||||
DefaultApigwProfiler = Profiler()
|
||||
DefaultApigwFilter = Filter()
|
||||
DefaultDataPrivacy = DataPrivacy(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service())
|
||||
DefaultSMTPChecker = SmtpConfigurationChecker(CurrentSettings, DefaultRenderer, DefaultAccessControl, c.Auth)
|
||||
DefaultExpression = Expression()
|
||||
|
||||
if err = initRoles(ctx, log.Named("rbac.roles"), c.RBAC, eventbus.Service(), rbac.Global()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Register a user resolver with the RBAC service so that contextual role
|
||||
// expressions can access user properties (email, username, handle, name, labels).
|
||||
rbac.Global().SetUserResolver(func(userID uint64) map[string]interface{} {
|
||||
u, err := store.LookupUserByID(ctx, s, userID)
|
||||
if err != nil || u == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load user labels
|
||||
if err = label.Load(ctx, s, u); err != nil {
|
||||
log.Warn("failed to load user labels for RBAC scope", zap.Uint64("userID", userID), zap.Error(err))
|
||||
}
|
||||
|
||||
// Convert labels to map[string]interface{} for expression evaluation.
|
||||
// Single-value labels are exposed as strings, multi-value as string slices.
|
||||
ll := make(map[string]interface{}, len(u.Labels))
|
||||
for k, v := range u.Labels {
|
||||
if len(v.Values) > 0 {
|
||||
ll[k] = v.Values
|
||||
} else {
|
||||
ll[k] = v.Val
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"email": u.Email,
|
||||
"username": u.Username,
|
||||
"handle": u.Handle,
|
||||
"name": u.Name,
|
||||
"labels": ll,
|
||||
}
|
||||
})
|
||||
|
||||
automationService.DefaultUser = DefaultUser
|
||||
|
||||
automationService.Registry().AddTypes(
|
||||
automation.User{},
|
||||
automation.Role{},
|
||||
automation.Template{},
|
||||
automation.RenderOptions{},
|
||||
automation.RenderedDocument{},
|
||||
automation.RbacResource{},
|
||||
automation.Reminder{},
|
||||
)
|
||||
|
||||
automation.UsersHandler(
|
||||
automationService.Registry(),
|
||||
DefaultUser,
|
||||
DefaultRole,
|
||||
)
|
||||
|
||||
automation.TemplatesHandler(
|
||||
automationService.Registry(),
|
||||
DefaultRenderer,
|
||||
)
|
||||
|
||||
automation.RolesHandler(
|
||||
automationService.Registry(),
|
||||
DefaultRole,
|
||||
DefaultUser,
|
||||
)
|
||||
automation.RemindersHandler(
|
||||
automationService.Registry(),
|
||||
DefaultReminder,
|
||||
)
|
||||
|
||||
automation.RbacHandler(
|
||||
automationService.Registry(),
|
||||
rbac.Global(),
|
||||
DefaultUser,
|
||||
DefaultRole,
|
||||
)
|
||||
|
||||
// Register notification handler
|
||||
automation.NotificationHandler(
|
||||
automationService.Registry(),
|
||||
DefaultNotification,
|
||||
DefaultUser,
|
||||
log,
|
||||
)
|
||||
|
||||
// ValuestoreHandler isn't (yet) a system thing but this initialization resides
|
||||
// here just so we can easily register it
|
||||
|
||||
automation.ValuestoreHandler(
|
||||
automationService.Registry(),
|
||||
valuestore.Global(),
|
||||
)
|
||||
|
||||
if c.ActionLog.WorkflowFunctionsEnabled {
|
||||
// register action-log functions & types only when enabled
|
||||
automation.ActionlogHandler(
|
||||
automationService.Registry(),
|
||||
DefaultActionlog,
|
||||
)
|
||||
|
||||
automationService.Registry().AddTypes(
|
||||
automation.Action{},
|
||||
)
|
||||
}
|
||||
|
||||
// Reload DAL sensitivity levels
|
||||
err = DefaultDalSensitivityLevel.ReloadSensitivityLevels(ctx, DefaultStore)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Reload DAL connections
|
||||
err = DefaultDalConnection.ReloadConnections(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Watchers(ctx context.Context) {
|
||||
DefaultReminder.Watch(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
func Activate(ctx context.Context) (err error) {
|
||||
// Run initial update of current settings
|
||||
err = DefaultSettings.UpdateCurrent(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = DefaultUserGroup.Activate(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// isGeneric returns true if given error is generic
|
||||
func isGeneric(err error) bool {
|
||||
g, ok := err.(interface{ IsGeneric() bool })
|
||||
return ok && g != nil && g.IsGeneric()
|
||||
}
|
||||
|
||||
// unwrapGeneric unwraps error if error is generic (and wrapped)
|
||||
func unwrapGeneric(err error) error {
|
||||
for {
|
||||
if isGeneric(err) {
|
||||
err = errors.Unwrap(err)
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Data is stale when new date does not match updatedAt or createdAt (before first update)
|
||||
//
|
||||
// @todo This is the same as in compose.service; do we want to make an util thing?
|
||||
func isStale(new *time.Time, updatedAt *time.Time, createdAt time.Time) bool {
|
||||
if new == nil {
|
||||
// Change to true for stale-data-check
|
||||
return false
|
||||
}
|
||||
|
||||
if updatedAt != nil {
|
||||
return !new.Equal(*updatedAt)
|
||||
}
|
||||
|
||||
return new.Equal(createdAt)
|
||||
}
|
||||
|
||||
func dartSassTranspiler(log *zap.Logger) *godartsass.Transpiler {
|
||||
transpiler, err := godartsass.Start(godartsass.Options{
|
||||
DartSassEmbeddedFilename: "sass",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Warn("dart sass is not installed in your system", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return transpiler
|
||||
}
|
||||
Reference in New Issue
Block a user