3
0

Implement permission provisioning and watchers

Remove rule reset from roles cli command
Add generic "provision" command for each binary and (re)set perm. rules
Permission rules are now separated and part of AccessControl service

Facility for watchers was added.
This commit is contained in:
Denis Arh
2019-05-10 09:49:07 +02:00
parent f113439d23
commit 2a4054c9bc
20 changed files with 377 additions and 166 deletions
+7
View File
@@ -50,11 +50,18 @@ func main() {
switch command {
case "help":
flag.PrintDefaults()
case "provision":
if err := compose.Provision(ctx); err != nil {
println("Failed to provision compose: ", err.Error())
os.Exit(1)
}
default:
// Checks subscription, will os.Exit(1) if there is an error
// Disabled for now, system service is the only one that validates subscription
// ctx = subscription.Monitor(ctx)
compose.StartWatchers(ctx)
if err := compose.StartRestAPI(ctx); err != nil {
log.Fatal("failed to start compose REST API", zap.Error(err))
}
+18
View File
@@ -78,6 +78,20 @@ func main() {
switch command {
case "help":
flag.PrintDefaults()
case "provision":
if err := system.Provision(ctx); err != nil {
println("Failed to provision system: ", err.Error())
os.Exit(1)
}
if err := compose.Provision(ctx); err != nil {
println("Failed to provision compose: ", err.Error())
os.Exit(1)
}
if err := messaging.Provision(ctx); err != nil {
println("Failed to provision messaging: ", err.Error())
os.Exit(1)
}
default:
// Initialize configuration of our services
if err := system.Init(ctx); err != nil {
@@ -103,6 +117,10 @@ func main() {
go metrics.NewMonitor(flags.monitor.Interval)
}
system.StartWatchers(ctx)
compose.StartWatchers(ctx)
messaging.StartWatchers(ctx)
r := chi.NewRouter()
// logging, cors and such
+6
View File
@@ -50,6 +50,12 @@ func main() {
switch command {
case "help":
flag.PrintDefaults()
case "provision":
if err := messaging.Provision(ctx); err != nil {
println("Failed to provision messagign: ", err.Error())
os.Exit(1)
}
default:
// Checks subscription, will os.Exit(1) if there is an error
// Disabled for now, system service is the only one that validates subscription
+5
View File
@@ -46,6 +46,11 @@ func main() {
switch command {
case "help":
flag.PrintDefaults()
case "provision":
if err := system.Provision(ctx); err != nil {
println("Failed to provision system: ", err.Error())
os.Exit(1)
}
default:
// Checks subscription, will os.Exit(1) if there is an error
ctx = subscription.Monitor(ctx)
+62 -3
View File
@@ -9,7 +9,12 @@ import (
type (
accessControl struct {
permissions permissions.Verifier
permissions accessControlPermissionServicer
}
accessControlPermissionServicer interface {
Can(context.Context, permissions.Resource, permissions.Operation, ...permissions.CheckAccessFunc) bool
Grant(context.Context, ...*permissions.Rule) error
}
permissionResource interface {
@@ -17,9 +22,9 @@ type (
}
)
func AccessControl(pv permissions.Verifier) *accessControl {
func AccessControl(perm accessControlPermissionServicer) *accessControl {
return &accessControl{
permissions: pv,
permissions: perm,
}
}
@@ -142,3 +147,57 @@ func (svc accessControl) CanDeletePage(ctx context.Context, r *types.Page) bool
func (svc accessControl) can(ctx context.Context, res permissionResource, op permissions.Operation, ff ...permissions.CheckAccessFunc) bool {
return svc.permissions.Can(ctx, res.PermissionResource(), op, ff...)
}
func (svc accessControl) Grant(ctx context.Context, rr ...*permissions.Rule) error {
return svc.permissions.Grant(ctx, rr...)
}
// DefaultRules returns list of default rules for this compose service
func (svc accessControl) DefaultRules() permissions.RuleSet {
var (
compose = types.ComposePermissionResource
namespaces = types.NamespacePermissionResource.AppendWildcard()
modules = types.ModulePermissionResource.AppendWildcard()
charts = types.ChartPermissionResource.AppendWildcard()
triggers = types.TriggerPermissionResource.AppendWildcard()
pages = types.PagePermissionResource.AppendWildcard()
allowAdm = func(res permissions.Resource, op permissions.Operation) *permissions.Rule {
return &permissions.Rule{
permissions.AdminRoleID,
res,
op,
permissions.Allow}
}
)
return permissions.RuleSet{
{permissions.EveryoneRoleID, compose, "access", permissions.Allow},
allowAdm(compose, "namespace.create"),
allowAdm(compose, "access"),
allowAdm(compose, "grant"),
allowAdm(namespaces, "read"),
allowAdm(namespaces, "update"),
allowAdm(namespaces, "delete"),
allowAdm(namespaces, "page.create"),
allowAdm(namespaces, "module.create"),
allowAdm(namespaces, "chart.create"),
allowAdm(namespaces, "trigger.create"),
allowAdm(modules, "read"),
allowAdm(modules, "update"),
allowAdm(modules, "delete"),
allowAdm(modules, "record.create"),
allowAdm(modules, "record.read"),
allowAdm(modules, "record.update"),
allowAdm(modules, "record.delete"),
allowAdm(charts, "read"),
allowAdm(charts, "update"),
allowAdm(charts, "delete"),
allowAdm(triggers, "read"),
allowAdm(triggers, "update"),
allowAdm(triggers, "delete"),
allowAdm(pages, "read"),
allowAdm(pages, "update"),
allowAdm(pages, "delete"),
}
}
+4 -2
View File
@@ -58,8 +58,10 @@ func TestMain(m *testing.M) {
}
}
systemService.Init()
Init()
ctx := context.Background()
systemService.Init(ctx)
Init(ctx)
os.Exit(m.Run())
}
+18 -5
View File
@@ -16,9 +16,16 @@ type (
db interface {
Transaction(callback func() error) error
}
permissionServicer interface {
accessControlPermissionServicer
Watch(ctx context.Context)
}
)
var (
permSvc permissionServicer
DefaultLogger *zap.Logger
DefaultAccessControl *accessControl
@@ -33,9 +40,7 @@ var (
DefaultNamespace NamespaceService
)
func Init() error {
ctx := context.Background()
func Init(ctx context.Context) error {
DefaultLogger = logger.Default().Named("compose.service")
fs, err := store.New("var/store")
@@ -43,8 +48,12 @@ func Init() error {
return err
}
pv := permissions.Service(permissions.Repository(repository.DB(ctx), "compose_permission_rules"))
DefaultAccessControl = AccessControl(pv)
permSvc = permissions.Service(
ctx,
DefaultLogger,
permissions.Repository(repository.DB(ctx), "compose_permission_rules"))
DefaultAccessControl = AccessControl(permSvc)
DefaultRecord = Record()
DefaultModule = Module()
@@ -58,6 +67,10 @@ func Init() error {
return nil
}
func Watchers(ctx context.Context) {
permSvc.Watch(ctx)
}
// Data is stale when new date does not match updatedAt or createdAt (before first update)
func isStale(new *time.Time, updatedAt *time.Time, createdAt time.Time) bool {
if new == nil {
+26
View File
@@ -0,0 +1,26 @@
package service
import (
"context"
"github.com/crusttech/crust/compose/internal/service"
)
// Provision orchestrates various tasks after deployment
//
func Provision(ctx context.Context) (err error) {
if err = resetDefaultPermissionRules(ctx); err != nil {
return
}
// @todo move migration here
return
}
// Resets default permission rules for compose resources
func resetDefaultPermissionRules(ctx context.Context) error {
var ac = service.DefaultAccessControl
return ac.Grant(ctx, ac.DefaultRules()...)
}
+5 -1
View File
@@ -46,7 +46,7 @@ func Init(ctx context.Context) (err error) {
}
// Don't change this to init(), it needs Database
return service.Init()
return service.Init(ctx)
}
func InitDatabase(ctx context.Context) error {
@@ -64,6 +64,10 @@ func InitDatabase(ctx context.Context) error {
return nil
}
func StartWatchers(ctx context.Context) {
service.Watchers(ctx)
}
func StartRestAPI(ctx context.Context) error {
logger.Default().Info("Starting HTTP server", zap.String("address", flags.http.Addr))
listener, err := net.Listen("tcp", flags.http.Addr)
+63 -3
View File
@@ -10,7 +10,12 @@ import (
type (
accessControl struct {
permissions permissions.Verifier
permissions accessControlPermissionServicer
}
accessControlPermissionServicer interface {
Can(context.Context, permissions.Resource, permissions.Operation, ...permissions.CheckAccessFunc) bool
Grant(context.Context, ...*permissions.Rule) error
}
permissionResource interface {
@@ -18,9 +23,9 @@ type (
}
)
func AccessControl(pv permissions.Verifier) *accessControl {
func AccessControl(perm accessControlPermissionServicer) *accessControl {
return &accessControl{
permissions: pv,
permissions: perm,
}
}
@@ -194,3 +199,58 @@ func (svc accessControl) isChannelOwnerFallback(ctx context.Context, ch *types.C
func (svc accessControl) can(ctx context.Context, res permissionResource, op permissions.Operation, ff ...permissions.CheckAccessFunc) bool {
return svc.permissions.Can(ctx, res.PermissionResource(), op, ff...)
}
func (svc accessControl) Grant(ctx context.Context, rr ...*permissions.Rule) error {
return svc.permissions.Grant(ctx, rr...)
}
// DefaultRules returns list of default rules for this compose service
func (svc accessControl) DefaultRules() permissions.RuleSet {
var (
messaging = types.MessagingPermissionResource
channels = types.ChannelPermissionResource.AppendWildcard()
webhooks = types.WebhookPermissionResource.AppendWildcard()
allowAdm = func(res permissions.Resource, op permissions.Operation) *permissions.Rule {
return &permissions.Rule{
permissions.AdminRoleID,
res,
op,
permissions.Allow}
}
)
return permissions.RuleSet{
{permissions.EveryoneRoleID, messaging, "access", permissions.Allow},
allowAdm(messaging, "access"),
allowAdm(messaging, "grant"),
allowAdm(messaging, "channel.public.create"),
allowAdm(messaging, "channel.private.create"),
allowAdm(messaging, "channel.group.create"),
allowAdm(channels, "update"),
allowAdm(channels, "leave"),
allowAdm(channels, "read"),
allowAdm(channels, "join"),
allowAdm(channels, "delete"),
allowAdm(channels, "undelete"),
allowAdm(channels, "archive"),
allowAdm(channels, "unarchive"),
allowAdm(channels, "members.manage"),
allowAdm(channels, "webhooks.manage"),
allowAdm(channels, "attachments.manage"),
allowAdm(channels, "message.attach"),
allowAdm(channels, "message.update.all"),
allowAdm(channels, "message.update.own"),
allowAdm(channels, "message.delete.all"),
allowAdm(channels, "message.delete.own"),
allowAdm(channels, "message.embed"),
allowAdm(channels, "message.send"),
allowAdm(channels, "message.reply"),
allowAdm(channels, "message.react"),
allowAdm(webhooks, "webhook.manage.all"),
allowAdm(webhooks, "webhook.manage.own"),
}
}
+3 -2
View File
@@ -7,6 +7,7 @@ import (
"os"
"testing"
"github.com/SentimensRG/ctx"
"github.com/namsral/flag"
"github.com/titpetric/factory"
"go.uber.org/zap/zapcore"
@@ -62,8 +63,8 @@ func TestMain(m *testing.M) {
}
}
systemService.Init()
Init()
systemService.Init(ctx.Background())
Init(ctx.Background())
os.Exit(m.Run())
}
+17 -5
View File
@@ -18,9 +18,16 @@ type (
db interface {
Transaction(callback func() error) error
}
permissionServicer interface {
accessControlPermissionServicer
Watch(ctx context.Context)
}
)
var (
permSvc permissionServicer
DefaultLogger *zap.Logger
DefaultAccessControl *accessControl
@@ -34,7 +41,7 @@ var (
DefaultWebhook WebhookService
)
func Init() error {
func Init(ctx context.Context) error {
fs, err := store.New("var/store")
if err != nil {
return err
@@ -49,10 +56,11 @@ func Init() error {
DefaultLogger = logger.Default().Named("messaging.service")
ctx := context.Background()
pv := permissions.Service(permissions.Repository(repository.DB(ctx), "compose_permission_rules"))
DefaultAccessControl = AccessControl(pv)
permSvc = permissions.Service(
ctx,
DefaultLogger,
permissions.Repository(repository.DB(ctx), "compose_permission_rules"))
DefaultAccessControl = AccessControl(permSvc)
DefaultEvent = Event(ctx)
DefaultChannel = Channel(ctx)
@@ -65,6 +73,10 @@ func Init() error {
return nil
}
func Watchers(ctx context.Context) {
permSvc.Watch(ctx)
}
func timeNowPtr() *time.Time {
now := time.Now()
return &now
+26
View File
@@ -0,0 +1,26 @@
package service
import (
"context"
"github.com/crusttech/crust/messaging/internal/service"
)
// Provision orchestrates various tasks after deployment
//
func Provision(ctx context.Context) (err error) {
if err = resetDefaultPermissionRules(ctx); err != nil {
return
}
// @todo move migration here
return
}
// Resets default permission rules for compose resources
func resetDefaultPermissionRules(ctx context.Context) error {
var ac = service.DefaultAccessControl
return ac.Grant(ctx, ac.DefaultRules()...)
}
+5 -1
View File
@@ -48,7 +48,7 @@ func Init(ctx context.Context) (err error) {
}
// Don't change this, it needs Database
service.Init()
service.Init(ctx)
return nil
}
@@ -68,6 +68,10 @@ func InitDatabase(ctx context.Context) error {
return nil
}
func StartWatchers(ctx context.Context) {
service.Watchers(ctx)
}
func StartRestAPI(ctx context.Context) error {
logger.Default().Info("Starting HTTP server", zap.String("address", flags.http.Addr))
listener, err := net.Listen("tcp", flags.http.Addr)
+1 -133
View File
@@ -2,15 +2,12 @@ package cli
import (
"context"
"fmt"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/titpetric/factory"
"github.com/crusttech/crust/internal/rules"
"github.com/crusttech/crust/system/internal/repository"
"github.com/crusttech/crust/system/types"
)
@@ -21,12 +18,6 @@ func rolesCmd(ctx context.Context, db *factory.DB) *cobra.Command {
Short: "Role management",
}
resetCmd := &cobra.Command{
Use: "reset",
Short: "Reset roles",
Run: rolesResetCmd(ctx, db),
}
addUserCmd := &cobra.Command{
Use: "useradd [role-ID-or-name-or-handle] [user-ID-or-email]",
Short: "Add user to role",
@@ -34,134 +25,11 @@ func rolesCmd(ctx context.Context, db *factory.DB) *cobra.Command {
Run: rolesUserAddCmd(ctx, db),
}
cmd.AddCommand(resetCmd, addUserCmd)
cmd.AddCommand(addUserCmd)
return cmd
}
func rolesResetCmd(ctx context.Context, db *factory.DB) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
var (
err error
roleRepo = repository.Role(ctx, db)
ruleRepo = rules.NewResources(ctx, db)
)
// Recreates core roles (will not reset memberships!)
err = types.RoleSet{
&types.Role{ID: 1, Handle: "everyone", Name: "Everyone"},
&types.Role{ID: 2, Handle: "admins", Name: "Administrators"},
}.Walk(func(r *types.Role) error {
// Update will exec REPLACE and take care of creation as well
r.CreatedAt = time.Now()
_, err := roleRepo.Update(r)
return err
})
if err != nil {
fmt.Printf("could not reset roles: %v", err)
return
}
rules := []rules.Rule{
{1, "system", "user.create", 2},
{1, "compose", "access", 2},
{1, "messaging", "access", 2},
{2, "compose", "namespace.create", 2},
{2, "compose", "access", 2},
{2, "compose", "grant", 2},
{2, "compose:namespace:*", "read", 2},
{2, "compose:namespace:*", "update", 2},
{2, "compose:namespace:*", "delete", 2},
{2, "compose:namespace:*", "page.create", 2},
{2, "compose:namespace:*", "module.create", 2},
{2, "compose:namespace:*", "chart.create", 2},
{2, "compose:namespace:*", "trigger.create", 2},
{2, "compose:chart:*", "read", 2},
{2, "compose:chart:*", "update", 2},
{2, "compose:chart:*", "delete", 2},
{2, "compose:trigger:*", "read", 2},
{2, "compose:trigger:*", "update", 2},
{2, "compose:trigger:*", "delete", 2},
{2, "compose:page:*", "read", 2},
{2, "compose:page:*", "update", 2},
{2, "compose:page:*", "delete", 2},
{2, "system", "access", 2},
{2, "system", "grant", 2},
{2, "system", "settings.read", 2},
{2, "system", "settings.manage", 2},
{2, "system", "organisation.create", 2},
{2, "system", "application.create", 2},
{2, "system", "user.create", 2},
{2, "system", "role.create", 2},
{2, "system:organisation:*", "access", 2},
{2, "system:application:*", "read", 2},
{2, "system:application:*", "update", 2},
{2, "system:application:*", "delete", 2},
{2, "system:user:*", "read", 2},
{2, "system:user:*", "update", 2},
{2, "system:user:*", "suspend", 2},
{2, "system:user:*", "unsuspend", 2},
{2, "system:user:*", "delete", 2},
{2, "system:role:*", "read", 2},
{2, "system:role:*", "update", 2},
{2, "system:role:*", "delete", 2},
{2, "system:role:*", "members.manage", 2},
{2, "messaging", "access", 2},
{2, "messaging", "grant", 2},
{2, "messaging", "channel.public.create", 2},
{2, "messaging", "channel.private.create", 2},
{2, "messaging", "channel.group.create", 2},
{2, "messaging:channel:*", "update", 2},
{2, "messaging:channel:*", "leave", 2},
{2, "messaging:channel:*", "read", 2},
{2, "messaging:channel:*", "join", 2},
{2, "messaging:channel:*", "delete", 2},
{2, "messaging:channel:*", "undelete", 2},
{2, "messaging:channel:*", "archive", 2},
{2, "messaging:channel:*", "unarchive", 2},
{2, "messaging:channel:*", "members.manage", 2},
{2, "messaging:channel:*", "webhooks.manage", 2},
{2, "messaging:channel:*", "attachments.manage", 2},
{2, "messaging:channel:*", "message.attach", 2},
{2, "messaging:channel:*", "message.update.all", 2},
{2, "messaging:channel:*", "message.update.own", 2},
{2, "messaging:channel:*", "message.delete.all", 2},
{2, "messaging:channel:*", "message.delete.own", 2},
{2, "messaging:channel:*", "message.embed", 2},
{2, "messaging:channel:*", "message.send", 2},
{2, "messaging:channel:*", "message.reply", 2},
{2, "messaging:channel:*", "message.react", 2},
{2, "compose:module:*", "read", 2},
{2, "compose:module:*", "update", 2},
{2, "compose:module:*", "delete", 2},
{2, "compose:module:*", "record.create", 2},
{2, "compose:module:*", "record.read", 2},
{2, "compose:module:*", "record.update", 2},
{2, "compose:module:*", "record.delete", 2},
}
var purgedRoles = map[uint64]bool{}
for _, r := range rules {
if !purgedRoles[r.RoleID] {
if err = ruleRepo.Delete(r.RoleID); err != nil {
fmt.Printf("could not reset rules: %v", err)
return
}
purgedRoles[r.RoleID] = true
}
}
// Recreates rules for core roles
err = ruleRepo.Reset(rules)
if err != nil {
fmt.Printf("could not reset rules: %v", err)
}
}
}
func rolesUserAddCmd(ctx context.Context, db *factory.DB) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
// Create role and user repository.
+57 -3
View File
@@ -9,7 +9,12 @@ import (
type (
accessControl struct {
permissions permissions.Verifier
permissions accessControlPermissionServicer
}
accessControlPermissionServicer interface {
Can(context.Context, permissions.Resource, permissions.Operation, ...permissions.CheckAccessFunc) bool
Grant(context.Context, ...*permissions.Rule) error
}
permissionResource interface {
@@ -17,9 +22,9 @@ type (
}
)
func AccessControl(pv permissions.Verifier) *accessControl {
func AccessControl(perm accessControlPermissionServicer) *accessControl {
return &accessControl{
permissions: pv,
permissions: perm,
}
}
@@ -113,3 +118,52 @@ func (svc accessControl) CanDeleteUser(ctx context.Context, u *types.User) bool
func (svc accessControl) can(ctx context.Context, res permissionResource, op permissions.Operation, ff ...permissions.CheckAccessFunc) bool {
return svc.permissions.Can(ctx, res.PermissionResource(), op, ff...)
}
func (svc accessControl) Grant(ctx context.Context, rr ...*permissions.Rule) error {
return svc.permissions.Grant(ctx, rr...)
}
// DefaultRules returns list of default rules for this compose service
func (svc accessControl) DefaultRules() permissions.RuleSet {
var (
sys = types.SystemPermissionResource
applications = types.ApplicationPermissionResource.AppendWildcard()
organisations = types.OrganisationPermissionResource.AppendWildcard()
roles = types.RolePermissionResource.AppendWildcard()
users = types.UserPermissionResource.AppendWildcard()
allowAdm = func(res permissions.Resource, op permissions.Operation) *permissions.Rule {
return &permissions.Rule{
permissions.AdminRoleID,
res,
op,
permissions.Allow}
}
)
return permissions.RuleSet{
{permissions.EveryoneRoleID, sys, "user.create", permissions.Allow},
allowAdm(sys, "access"),
allowAdm(sys, "grant"),
allowAdm(sys, "settings.read"),
allowAdm(sys, "settings.manage"),
allowAdm(sys, "organisation.create"),
allowAdm(sys, "application.create"),
allowAdm(sys, "user.create"),
allowAdm(sys, "role.create"),
allowAdm(organisations, "access"),
allowAdm(applications, "read"),
allowAdm(applications, "update"),
allowAdm(applications, "delete"),
allowAdm(users, "read"),
allowAdm(users, "update"),
allowAdm(users, "suspend"),
allowAdm(users, "unsuspend"),
allowAdm(users, "delete"),
allowAdm(roles, "read"),
allowAdm(roles, "update"),
allowAdm(roles, "delete"),
allowAdm(roles, "members.manage"),
}
}
+15 -5
View File
@@ -15,9 +15,14 @@ type (
db interface {
Transaction(callback func() error) error
}
permissionServicer interface {
accessControlPermissionServicer
Watch(ctx context.Context)
}
)
var (
permSvc permissionServicer
DefaultLogger *zap.Logger
DefaultAccessControl *accessControl
@@ -33,15 +38,16 @@ var (
DefaultApplication ApplicationService
)
func Init() (err error) {
ctx := context.Background()
func Init(ctx context.Context) (err error) {
intSet := internalSettings.NewService(internalSettings.NewRepository(repository.DB(ctx), "sys_settings"))
DefaultLogger = logger.Default().Named("system.service")
pv := permissions.Service(permissions.Repository(repository.DB(ctx), "compose_permission_rules"))
DefaultAccessControl = AccessControl(pv)
permSvc = permissions.Service(
ctx,
DefaultLogger,
permissions.Repository(repository.DB(ctx), "compose_permission_rules"))
DefaultAccessControl = AccessControl(permSvc)
DefaultSettings = Settings(ctx, intSet)
@@ -60,3 +66,7 @@ func Init() (err error) {
return
}
func Watchers(ctx context.Context) {
permSvc.Watch(ctx)
}
+26
View File
@@ -0,0 +1,26 @@
package service
import (
"context"
"github.com/crusttech/crust/system/internal/service"
)
// Provision orchestrates various tasks after deployment
//
func Provision(ctx context.Context) (err error) {
if err = resetDefaultPermissionRules(ctx); err != nil {
return
}
// @todo move migration here
return
}
// Resets default permission rules for compose resources
func resetDefaultPermissionRules(ctx context.Context) error {
var ac = service.DefaultAccessControl
return ac.Grant(ctx, ac.DefaultRules()...)
}
+8 -2
View File
@@ -1,13 +1,19 @@
package service
import (
"context"
"github.com/crusttech/crust/system/internal/service"
)
func Init() error {
err := service.Init()
func Init(ctx context.Context) error {
err := service.Init(ctx)
DefaultRole = service.DefaultRole
// DefaultRules = service.DefaultRules
DefaultUser = service.DefaultUser
return err
}
func Watchers(ctx context.Context) {
service.Watchers(ctx)
}
+5 -1
View File
@@ -51,7 +51,7 @@ func Init(ctx context.Context) (err error) {
}
// Don't change this, it needs database connection
if err = service.Init(); err != nil {
if err = service.Init(ctx); err != nil {
return
}
@@ -73,6 +73,10 @@ func InitDatabase(ctx context.Context) error {
return nil
}
func StartWatchers(ctx context.Context) {
service.Watchers(ctx)
}
func StartRestAPI(ctx context.Context) error {
// Load settings from the database,
// for now, only at start-up time.