From 8d90c7042f93fc8d4d30e973d79c59e6973c2c1b Mon Sep 17 00:00:00 2001 From: Ibrahim Serdar Acikgoz Date: Wed, 14 Dec 2022 15:24:04 +0300 Subject: [PATCH] [MM-48883] Remove app package dependency for products (#21853) --- app/channels.go | 53 ++++++++++--------- app/license.go | 4 +- app/notification_push_test.go | 14 +++--- app/product.go | 24 ++------- app/product_test.go | 95 ++++++++++++++++++----------------- app/server.go | 57 +++++++-------------- product/product.go | 24 +++++++++ product/service.go | 28 +++++++++++ 8 files changed, 162 insertions(+), 137 deletions(-) create mode 100644 product/product.go create mode 100644 product/service.go diff --git a/app/channels.go b/app/channels.go index e4a25f3749..31eca3a2ea 100644 --- a/app/channels.go +++ b/app/channels.go @@ -23,6 +23,8 @@ import ( "github.com/mattermost/mattermost-server/v6/shared/mlog" ) +const ServerKey product.ServiceKey = "server" + // licenseSvc is added to act as a starting point for future integrated products. // It has the same signature and functionality with the license related APIs of the plugin-api. type licenseSvc interface { @@ -86,19 +88,24 @@ type Channels struct { } func init() { - RegisterProduct("channels", ProductManifest{ - Initializer: func(s *Server, services map[ServiceKey]any) (Product, error) { - return NewChannels(s, services) + product.RegisterProduct("channels", product.Manifest{ + Initializer: func(services map[product.ServiceKey]any) (product.Product, error) { + return NewChannels(services) }, - Dependencies: map[ServiceKey]struct{}{ - ConfigKey: {}, - LicenseKey: {}, - FilestoreKey: {}, + Dependencies: map[product.ServiceKey]struct{}{ + ServerKey: {}, + product.ConfigKey: {}, + product.LicenseKey: {}, + product.FilestoreKey: {}, }, }) } -func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) { +func NewChannels(services map[product.ServiceKey]any) (*Channels, error) { + s, ok := services[ServerKey].(*Server) + if !ok { + return nil, errors.New("server not passed") + } ch := &Channels{ srv: s, imageProxy: imageproxy.MakeImageProxy(s.platform, s.httpService, s.Log()), @@ -112,10 +119,10 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) { // 2. Add the field to *Channels // 3. Add the service key to the slice. // 4. Add a new case in the switch statement. - requiredServices := []ServiceKey{ - ConfigKey, - LicenseKey, - FilestoreKey, + requiredServices := []product.ServiceKey{ + product.ConfigKey, + product.LicenseKey, + product.FilestoreKey, } for _, svcKey := range requiredServices { svc, ok := services[svcKey] @@ -124,19 +131,19 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) { } switch svcKey { // Keep adding more services here - case ConfigKey: + case product.ConfigKey: cfgSvc, ok := svc.(product.ConfigService) if !ok { return nil, errors.New("Config service did not satisfy ConfigSvc interface") } ch.cfgSvc = cfgSvc - case FilestoreKey: + case product.FilestoreKey: filestore, ok := svc.(filestore.FileBackend) if !ok { return nil, errors.New("Filestore service did not satisfy FileBackend interface") } ch.filestore = filestore - case LicenseKey: + case product.LicenseKey: svc, ok := svc.(licenseSvc) if !ok { return nil, errors.New("License service did not satisfy licenseSvc interface") @@ -198,7 +205,7 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) { } ch.routerSvc = newRouterService() - services[RouterKey] = ch.routerSvc + services[product.RouterKey] = ch.routerSvc // Setup routes. pluginsRoute := ch.srv.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() @@ -206,29 +213,29 @@ func NewChannels(s *Server, services map[ServiceKey]any) (*Channels, error) { pluginsRoute.HandleFunc("/public/{public_file:.*}", ch.ServePluginPublicRequest) pluginsRoute.HandleFunc("/{anything:.*}", ch.ServePluginRequest) - services[PostKey] = &postServiceWrapper{ + services[product.PostKey] = &postServiceWrapper{ app: &App{ch: ch}, } - services[PermissionsKey] = &permissionsServiceWrapper{ + services[product.PermissionsKey] = &permissionsServiceWrapper{ app: &App{ch: ch}, } - services[TeamKey] = &teamServiceWrapper{ + services[product.TeamKey] = &teamServiceWrapper{ app: &App{ch: ch}, } - services[BotKey] = &botServiceWrapper{ + services[product.BotKey] = &botServiceWrapper{ app: &App{ch: ch}, } - services[HooksKey] = &hooksService{ + services[product.HooksKey] = &hooksService{ ch: ch, } - services[UserKey] = &App{ch: ch} + services[product.UserKey] = &App{ch: ch} - services[PreferencesKey] = &preferencesServiceWrapper{ + services[product.PreferencesKey] = &preferencesServiceWrapper{ app: &App{ch: ch}, } diff --git a/app/license.go b/app/license.go index 9afff64349..61b4d2dc85 100644 --- a/app/license.go +++ b/app/license.go @@ -32,8 +32,8 @@ type licenseWrapper struct { srv *Server } -func (w *licenseWrapper) Name() ServiceKey { - return LicenseKey +func (w *licenseWrapper) Name() product.ServiceKey { + return product.LicenseKey } func (w *licenseWrapper) GetLicense() *model.License { diff --git a/app/notification_push_test.go b/app/notification_push_test.go index 73bf38e72b..7c8a6d78c1 100644 --- a/app/notification_push_test.go +++ b/app/notification_push_test.go @@ -20,6 +20,7 @@ import ( "github.com/mattermost/mattermost-server/v6/app/platform" "github.com/mattermost/mattermost-server/v6/config" "github.com/mattermost/mattermost-server/v6/model" + "github.com/mattermost/mattermost-server/v6/product" fmocks "github.com/mattermost/mattermost-server/v6/shared/filestore/mocks" "github.com/mattermost/mattermost-server/v6/shared/i18n" "github.com/mattermost/mattermost-server/v6/store/storetest/mocks" @@ -1440,7 +1441,7 @@ func TestPushNotificationRace(t *testing.T) { Return(&model.Preference{Value: "test"}, nil) mockStore.On("Preference").Return(&mockPreferenceStore) s := &Server{ - products: make(map[string]Product), + products: make(map[string]product.Product), Router: mux.NewRouter(), } var err error @@ -1449,12 +1450,13 @@ func TestPushNotificationRace(t *testing.T) { }, platform.SetFileStore(&fmocks.FileBackend{})) s.SetStore(mockStore) require.NoError(t, err) - serviceMap := map[ServiceKey]any{ - ConfigKey: s.platform, - LicenseKey: &licenseWrapper{s}, - FilestoreKey: s.FileBackend(), + serviceMap := map[product.ServiceKey]any{ + ServerKey: s, + product.ConfigKey: s.platform, + product.LicenseKey: &licenseWrapper{s}, + product.FilestoreKey: s.FileBackend(), } - ch, err := NewChannels(s, serviceMap) + ch, err := NewChannels(serviceMap) require.NoError(t, err) s.products["channels"] = ch diff --git a/app/product.go b/app/product.go index a8c15a9e0a..9183e40ea8 100644 --- a/app/product.go +++ b/app/product.go @@ -6,27 +6,13 @@ package app import ( "fmt" "strings" + + "github.com/mattermost/mattermost-server/v6/product" ) -type Product interface { - Start() error - Stop() error -} - -type ProductManifest struct { - Initializer func(*Server, map[ServiceKey]any) (Product, error) - Dependencies map[ServiceKey]struct{} -} - -var products = make(map[string]ProductManifest) - -func RegisterProduct(name string, m ProductManifest) { - products[name] = m -} - func (s *Server) initializeProducts( - productMap map[string]ProductManifest, - serviceMap map[ServiceKey]any, + productMap map[string]product.Manifest, + serviceMap map[product.ServiceKey]any, ) error { // create a product map to consume pmap := make(map[string]struct{}) @@ -57,7 +43,7 @@ func (s *Server) initializeProducts( // some products can register themselves/their services initializer := manifest.Initializer - prod, err := initializer(s, serviceMap) + prod, err := initializer(serviceMap) if err != nil { return fmt.Errorf("error initializing product %q: %w", product, err) } diff --git a/app/product_test.go b/app/product_test.go index bafba19cbd..66d0ba6dcf 100644 --- a/app/product_test.go +++ b/app/product_test.go @@ -6,6 +6,7 @@ package app import ( "testing" + "github.com/mattermost/mattermost-server/v6/product" "github.com/stretchr/testify/require" ) @@ -16,7 +17,7 @@ const ( type productA struct{} -func newProductA(s *Server, m map[ServiceKey]any) (Product, error) { +func newProductA(m map[product.ServiceKey]any) (product.Product, error) { m[testSrvKey1] = nil return &productA{}, nil } @@ -26,7 +27,7 @@ func (p *productA) Stop() error { return nil } type productB struct{} -func newProductB(s *Server, m map[ServiceKey]any) (Product, error) { +func newProductB(m map[product.ServiceKey]any) (product.Product, error) { m[testSrvKey2] = nil return &productB{}, nil } @@ -36,35 +37,35 @@ func (p *productB) Stop() error { return nil } func TestInitializeProducts(t *testing.T) { t.Run("2 products and no circular dependency", func(t *testing.T) { - serviceMap := map[ServiceKey]any{ - ConfigKey: nil, - LicenseKey: nil, - FilestoreKey: nil, - ClusterKey: nil, + serviceMap := map[product.ServiceKey]any{ + product.ConfigKey: nil, + product.LicenseKey: nil, + product.FilestoreKey: nil, + product.ClusterKey: nil, } - products := map[string]ProductManifest{ + products := map[string]product.Manifest{ "productA": { Initializer: newProductA, - Dependencies: map[ServiceKey]struct{}{ - ConfigKey: {}, - LicenseKey: {}, - FilestoreKey: {}, - ClusterKey: {}, + Dependencies: map[product.ServiceKey]struct{}{ + product.ConfigKey: {}, + product.LicenseKey: {}, + product.FilestoreKey: {}, + product.ClusterKey: {}, }, }, "productB": { Initializer: newProductB, - Dependencies: map[ServiceKey]struct{}{ - ConfigKey: {}, - testSrvKey1: {}, - FilestoreKey: {}, - ClusterKey: {}, + Dependencies: map[product.ServiceKey]struct{}{ + product.ConfigKey: {}, + testSrvKey1: {}, + product.FilestoreKey: {}, + product.ClusterKey: {}, }, }, } server := &Server{ - products: make(map[string]Product), + products: make(map[string]product.Product), } err := server.initializeProducts(products, serviceMap) @@ -73,36 +74,36 @@ func TestInitializeProducts(t *testing.T) { }) t.Run("2 products and circular dependency", func(t *testing.T) { - serviceMap := map[ServiceKey]any{ - ConfigKey: nil, - LicenseKey: nil, - FilestoreKey: nil, - ClusterKey: nil, + serviceMap := map[product.ServiceKey]any{ + product.ConfigKey: nil, + product.LicenseKey: nil, + product.FilestoreKey: nil, + product.ClusterKey: nil, } - products := map[string]ProductManifest{ + products := map[string]product.Manifest{ "productA": { Initializer: newProductA, - Dependencies: map[ServiceKey]struct{}{ - ConfigKey: {}, - LicenseKey: {}, - FilestoreKey: {}, - ClusterKey: {}, - testSrvKey2: {}, + Dependencies: map[product.ServiceKey]struct{}{ + product.ConfigKey: {}, + product.LicenseKey: {}, + product.FilestoreKey: {}, + product.ClusterKey: {}, + testSrvKey2: {}, }, }, "productB": { Initializer: newProductB, - Dependencies: map[ServiceKey]struct{}{ - ConfigKey: {}, - testSrvKey1: {}, - FilestoreKey: {}, - ClusterKey: {}, + Dependencies: map[product.ServiceKey]struct{}{ + product.ConfigKey: {}, + testSrvKey1: {}, + product.FilestoreKey: {}, + product.ClusterKey: {}, }, }, } server := &Server{ - products: make(map[string]Product), + products: make(map[string]product.Product), } err := server.initializeProducts(products, serviceMap) @@ -110,19 +111,19 @@ func TestInitializeProducts(t *testing.T) { }) t.Run("2 products and one w/o any dependency", func(t *testing.T) { - serviceMap := map[ServiceKey]any{ - ConfigKey: nil, - LicenseKey: nil, - FilestoreKey: nil, - ClusterKey: nil, + serviceMap := map[product.ServiceKey]any{ + product.ConfigKey: nil, + product.LicenseKey: nil, + product.FilestoreKey: nil, + product.ClusterKey: nil, } - products := map[string]ProductManifest{ + products := map[string]product.Manifest{ "productA": { Initializer: newProductA, - Dependencies: map[ServiceKey]struct{}{ - ConfigKey: {}, - LicenseKey: {}, + Dependencies: map[product.ServiceKey]struct{}{ + product.ConfigKey: {}, + product.LicenseKey: {}, }, }, "productB": { @@ -130,7 +131,7 @@ func TestInitializeProducts(t *testing.T) { }, } server := &Server{ - products: make(map[string]Product), + products: make(map[string]product.Product), } err := server.initializeProducts(products, serviceMap) diff --git a/app/server.go b/app/server.go index d39e66705c..fbd433a6db 100644 --- a/app/server.go +++ b/app/server.go @@ -75,30 +75,6 @@ import ( // declaring this as var to allow overriding in tests var SentryDSN = "placeholder_sentry_dsn" -type ServiceKey string - -const ( - ChannelKey ServiceKey = "channel" - ConfigKey ServiceKey = "config" - LicenseKey ServiceKey = "license" - FilestoreKey ServiceKey = "filestore" - FileInfoStoreKey ServiceKey = "fileinfostore" - ClusterKey ServiceKey = "cluster" - CloudKey ServiceKey = "cloud" - PostKey ServiceKey = "post" - TeamKey ServiceKey = "team" - UserKey ServiceKey = "user" - PermissionsKey ServiceKey = "permissions" - RouterKey ServiceKey = "router" - BotKey ServiceKey = "bot" - LogKey ServiceKey = "log" - HooksKey ServiceKey = "hooks" - KVStoreKey ServiceKey = "kvstore" - StoreKey ServiceKey = "storekey" - SystemKey ServiceKey = "systemkey" - PreferencesKey ServiceKey = "preferenceskey" -) - type Server struct { // RootRouter is the starting point for all HTTP requests to the server. RootRouter *mux.Router @@ -160,7 +136,7 @@ type Server struct { tracer *tracing.Tracer - products map[string]Product + products map[string]product.Product hooksManager *product.HooksManager } @@ -187,7 +163,7 @@ func NewServer(options ...Option) (*Server, error) { RootRouter: rootRouter, LocalRouter: localRouter, timezones: timezones.New(), - products: make(map[string]Product), + products: make(map[string]product.Product), } for _, option := range options { @@ -262,24 +238,25 @@ func NewServer(options ...Option) (*Server, error) { // ensure app implements `product.UserService` var _ product.UserService = (*App)(nil) - serviceMap := map[ServiceKey]any{ - ChannelKey: &channelsWrapper{srv: s}, - ConfigKey: s.platform, - LicenseKey: s.licenseWrapper, - FilestoreKey: s.platform.FileBackend(), - FileInfoStoreKey: &fileInfoWrapper{srv: s}, - ClusterKey: s.platform, - UserKey: New(ServerConnector(s.Channels())), - LogKey: s.platform.Log(), - CloudKey: &cloudWrapper{cloud: s.Cloud}, - KVStoreKey: s.platform, - StoreKey: store.NewStoreServiceAdapter(s.Store()), - SystemKey: &systemServiceAdapter{server: s}, + serviceMap := map[product.ServiceKey]any{ + ServerKey: s, + product.ChannelKey: &channelsWrapper{srv: s}, + product.ConfigKey: s.platform, + product.LicenseKey: s.licenseWrapper, + product.FilestoreKey: s.platform.FileBackend(), + product.FileInfoStoreKey: &fileInfoWrapper{srv: s}, + product.ClusterKey: s.platform, + product.UserKey: New(ServerConnector(s.Channels())), + product.LogKey: s.platform.Log(), + product.CloudKey: &cloudWrapper{cloud: s.Cloud}, + product.KVStoreKey: s.platform, + product.StoreKey: store.NewStoreServiceAdapter(s.Store()), + product.SystemKey: &systemServiceAdapter{server: s}, } // Step 4: Initialize products. // Depends on s.httpService. - err = s.initializeProducts(products, serviceMap) + err = s.initializeProducts(product.GetProducts(), serviceMap) if err != nil { return nil, errors.Wrap(err, "failed to initialize products") } diff --git a/product/product.go b/product/product.go new file mode 100644 index 0000000000..df084ff015 --- /dev/null +++ b/product/product.go @@ -0,0 +1,24 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package product + +type Product interface { + Start() error + Stop() error +} + +type Manifest struct { + Initializer func(map[ServiceKey]any) (Product, error) + Dependencies map[ServiceKey]struct{} +} + +var products = make(map[string]Manifest) + +func RegisterProduct(name string, m Manifest) { + products[name] = m +} + +func GetProducts() map[string]Manifest { + return products +} diff --git a/product/service.go b/product/service.go new file mode 100644 index 0000000000..221fa1c168 --- /dev/null +++ b/product/service.go @@ -0,0 +1,28 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package product + +type ServiceKey string + +const ( + ChannelKey ServiceKey = "channel" + ConfigKey ServiceKey = "config" + LicenseKey ServiceKey = "license" + FilestoreKey ServiceKey = "filestore" + FileInfoStoreKey ServiceKey = "fileinfostore" + ClusterKey ServiceKey = "cluster" + CloudKey ServiceKey = "cloud" + PostKey ServiceKey = "post" + TeamKey ServiceKey = "team" + UserKey ServiceKey = "user" + PermissionsKey ServiceKey = "permissions" + RouterKey ServiceKey = "router" + BotKey ServiceKey = "bot" + LogKey ServiceKey = "log" + HooksKey ServiceKey = "hooks" + KVStoreKey ServiceKey = "kvstore" + StoreKey ServiceKey = "storekey" + SystemKey ServiceKey = "systemkey" + PreferencesKey ServiceKey = "preferenceskey" +)