3
0

366 lines
10 KiB
Go

package service
import (
"context"
"errors"
"github.com/bep/godartsass/v2"
"time"
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/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
DefaultApplication *application
DefaultReminder ReminderService
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())
DefaultApplication = Application(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service())
DefaultReminder = Reminder(ctx, DefaultLogger.Named("reminder"), 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
}
automationService.DefaultUser = DefaultUser
automationService.Registry().AddTypes(
automation.User{},
automation.Role{},
automation.Template{},
automation.RenderOptions{},
automation.RenderedDocument{},
automation.RbacResource{},
)
automation.UsersHandler(
automationService.Registry(),
DefaultUser,
DefaultRole,
)
automation.TemplatesHandler(
automationService.Registry(),
DefaultRenderer,
)
automation.RolesHandler(
automationService.Registry(),
DefaultRole,
DefaultUser,
)
automation.RbacHandler(
automationService.Registry(),
rbac.Global(),
DefaultUser,
DefaultRole,
)
// 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
}
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
}