diff --git a/app-service-template/Attribution.txt b/app-service-template/Attribution.txt index d0f067243..356241ce2 100644 --- a/app-service-template/Attribution.txt +++ b/app-service-template/Attribution.txt @@ -175,4 +175,7 @@ golang.org/x/sys (Unspecified) https://github.com/golang/sys https://github.com/golang/sys/blob/master/LICENSE stretchr/objx (MIT) https://github.com/stretchr/objx -https://github.com/stretchr/objx/blob/master/LICENSE \ No newline at end of file +https://github.com/stretchr/objx/blob/master/LICENSE + +github.com/gorilla/websocket (BSD-2) https://github.com/gorilla/websocket +https://github.com/gorilla/websocket/blob/master/LICENSE \ No newline at end of file diff --git a/app-service-template/config/configuration.go b/app-service-template/config/configuration.go new file mode 100644 index 000000000..c8c013736 --- /dev/null +++ b/app-service-template/config/configuration.go @@ -0,0 +1,127 @@ +// TODO: Change Copyright to your company if open sourcing or remove header +// +// Copyright (c) 2021 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package config + +// This file contains example of custom configuration that can be loaded from the service's configuration.toml +// and/or the Configuration Provider, aka Consul (if enabled). +// For more details see https://docs.edgexfoundry.org/2.0/microservices/application/GeneralAppServiceConfig/#custom-configuration +// TODO: Update this configuration as needed for you service's needs and remove this comment +// or remove this file if not using custom configuration. + +import ( + "errors" + "reflect" + + "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger" +) + +// TODO: Define your structured custom configuration types. Must be wrapped with an outer struct with +// single element that matches the top level custom configuration element in your configuration.toml file, +// 'AppCustom' in this example. Replace this example with your configuration structure or +// remove this file if not using structured custom configuration. +type ServiceConfig struct { + AppCustom AppCustomConfig +} + +// AppCustomConfig is example of service's custom structured configuration that is specified in the service's +// configuration.toml file and Configuration Provider (aka Consul), if enabled. +type AppCustomConfig struct { + ResourceNames string + SomeValue int + SomeService HostInfo +} + +// HostInfo is example struct for defining connection information for external service +type HostInfo struct { + Host string + Port int + Protocol string +} + +// TODO: Update using your Custom configuration type. +// UpdateFromRaw updates the service's full configuration from raw data received from +// the Service Provider. +func (c *ServiceConfig) UpdateFromRaw(rawConfig interface{}) bool { + configuration, ok := rawConfig.(*ServiceConfig) + if !ok { + return false //errors.New("unable to cast raw config to type 'ServiceConfig'") + } + + *c = *configuration + + return true +} + +// TODO: Update using your Custom configuration 'writeable' type or remove if not using ListenForCustomConfigChanges +// UpdateWritableFromRaw updates the service's writable configuration from raw data received from +// the Service Provider. Must implement if using ListenForCustomConfigChanges, otherwise this can be removed. +func (ac *AppCustomConfig) UpdateWritableFromRaw(rawWritableConfig interface{}) bool { + appCustom, ok := rawWritableConfig.(*AppCustomConfig) + if !ok { + return false //errors.New("unable to cast raw writeable config to type 'AppCustomConfig'") + } + + *ac = *appCustom + + return true +} + +// WaitForCustomConfigChanges waits for indication that the custom configuration section has been updated and then process +// the changes as needed +// TODO: Update to use your custom configuration section that you want to be writable (i.e. runtime changes from Consul) +// or remove if not using custom configuration section or writable custom configuration. +func (ac *AppCustomConfig) WaitForCustomConfigChanges(configChanged chan bool, lc logger.LoggingClient) { + previous := *ac // Copy for change detection + + go func() { + for { + select { + case <-configChanged: + // TODO: Process the changed configuration. + // Must keep a previous copy of the configuration to determine what has changed. + // Replace the examples below with your appropriate processing logic. + switch { + case previous.SomeValue != ac.SomeValue: + lc.Infof("AppCustom.SomeValue changed to: %d", ac.SomeValue) + case previous.ResourceNames != ac.ResourceNames: + lc.Infof("AppCustom.ResourceNames changed to: %s", ac.ResourceNames) + case !reflect.DeepEqual(previous.SomeService, ac.SomeService): + lc.Infof("AppCustom.SomeService changed to: %v", ac.SomeService) + default: + lc.Info("No changes detected") + } + + previous = *ac + } + } + }() +} + +// Validate ensures your custom configuration has proper values. +// TODO: Update to properly validate your custom configuration +func (ac *AppCustomConfig) Validate() error { + if ac.SomeValue <= 0 { + return errors.New("SomeValue must be greater than zero") + } + + if reflect.DeepEqual(ac.SomeService, HostInfo{}) { + return errors.New("SomeService is not set") + } + + return nil +} diff --git a/app-service-template/main.go b/app-service-template/main.go index e916402f8..ddea33053 100644 --- a/app-service-template/main.go +++ b/app-service-template/main.go @@ -20,9 +20,12 @@ package main import ( "os" + "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger" + "github.com/edgexfoundry/app-functions-sdk-go/v2/pkg" "github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/interfaces" "github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/transforms" + "new-app-service/config" "new-app-service/functions" ) @@ -31,45 +34,86 @@ const ( serviceKey = "new-app-service" ) +// TODO: Define your app's struct +type myApp struct { + service interfaces.ApplicationService + lc logger.LoggingClient + serviceConfig *config.ServiceConfig + configChanged chan bool +} + func main() { - // TODO: See https://docs.edgexfoundry.org/1.3/microservices/application/ApplicationServices/ + // TODO: See https://docs.edgexfoundry.org/2.0/microservices/application/ApplicationServices/ // for documentation on application services. - - code := CreateAndRunService(serviceKey, pkg.NewAppService) + app := myApp{} + code := app.CreateAndRunAppService(serviceKey, pkg.NewAppService) os.Exit(code) } -// CreateAndRunService wraps what would normally be in main() so that it can be unit tested -func CreateAndRunService(serviceKey string, newServiceFactory func(string) (interfaces.ApplicationService, bool)) int { - service, ok := newServiceFactory(serviceKey) +// CreateAndRunAppService wraps what would normally be in main() so that it can be unit tested +// TODO: Remove and just use regular main() if unit tests of main logic not needed. +func (app *myApp) CreateAndRunAppService(serviceKey string, newServiceFactory func(string) (interfaces.ApplicationService, bool)) int { + var ok bool + app.service, ok = newServiceFactory(serviceKey) if !ok { return -1 } - lc := service.LoggingClient() + app.lc = app.service.LoggingClient() + + // TODO: Replace with retrieving your custom ApplicationSettings from configuration or + // remove if not using AppSetting configuration section. + // For more details see https://docs.edgexfoundry.org/2.0/microservices/application/GeneralAppServiceConfig/#application-settings + deviceNames, err := app.service.GetAppSettingStrings("DeviceNames") + if err != nil { + app.lc.Errorf("failed to retrieve DeviceNames from configuration: %s", err.Error()) + return -1 + } + + // More advance custom structured configuration can be defined and loaded as in this example. + // For more details see https://docs.edgexfoundry.org/2.0/microservices/application/GeneralAppServiceConfig/#custom-configuration + // TODO: Change to use your service's custom configuration struct + // or remove if not using custom configuration capability + app.serviceConfig = &config.ServiceConfig{} + if err := app.service.LoadCustomConfig(app.serviceConfig, "AppCustom"); err != nil { + app.lc.Errorf("failed load custom configuration: %s", err.Error()) + return -1 + } + + // Optionally validate the custom configuration after it is loaded. + // TODO: remove if you don't have custom configuration or don't need to validate it + if err := app.serviceConfig.AppCustom.Validate(); err != nil { + app.lc.Errorf("custom configuration failed validation: %s", err.Error()) + return -1 + } - // TODO: Replace with retrieving your custom ApplicationSettings from configuration - deviceNames, err := service.GetAppSettingStrings("DeviceNames") + // Custom configuration can be 'writable' or a section of the configuration can be 'writable' when using + // the Configuration Provider, aka Consul. + // For more details see https://docs.edgexfoundry.org/2.0/microservices/application/GeneralAppServiceConfig/#writable-custom-configuration + // TODO: Remove if not using writable custom configuration + configChanged := make(chan bool) + err = app.service.ListenForCustomConfigChanges(&app.serviceConfig.AppCustom, "AppCustom", configChanged) if err != nil { - lc.Errorf("failed to retrieve DeviceNames from configuration: %s", err.Error()) + app.lc.Errorf("unable to watch custom writable configuration: %s", err.Error()) return -1 } + app.serviceConfig.AppCustom.WaitForCustomConfigChanges(configChanged, app.lc) // TODO: Replace below functions with built in and/or your custom functions for your use case. - // See https://docs.edgexfoundry.org/1.3/microservices/application/BuiltIn/ for list of built-in functions + // See https://docs.edgexfoundry.org/2.0/microservices/application/BuiltIn/ for list of built-in functions sample := functions.NewSample() - err = service.SetFunctionsPipeline( + err = app.service.SetFunctionsPipeline( transforms.NewFilterFor(deviceNames).FilterByDeviceName, sample.LogEventDetails, sample.ConvertEventToXML, sample.OutputXML) if err != nil { - lc.Errorf("SetFunctionsPipeline returned error: %s", err.Error()) + app.lc.Errorf("SetFunctionsPipeline returned error: %s", err.Error()) return -1 } - if err := service.MakeItRun(); err != nil { - lc.Errorf("MakeItRun returned error: %s", err.Error()) + if err := app.service.MakeItRun(); err != nil { + app.lc.Errorf("MakeItRun returned error: %s", err.Error()) return -1 } diff --git a/app-service-template/main_test.go b/app-service-template/main_test.go index 1e8c7fada..53deade2e 100644 --- a/app-service-template/main_test.go +++ b/app-service-template/main_test.go @@ -31,7 +31,7 @@ import ( // This is an example of how to test the code that would typically be in the main() function use mocks // Not to helpful for a simple main() , but can be if the main() has more complexity that should be unit tested -// TODO: add/update tests for your customized CreateAndRunService or remove for simple main() +// TODO: add/update tests for your customized CreateAndRunAppService or remove if your main code doesn't require unit testing. func TestCreateAndRunService_Success(t *testing.T) { mockFactory := func(_ string) (interfaces.ApplicationService, bool) { @@ -41,13 +41,18 @@ func TestCreateAndRunService_Success(t *testing.T) { Return([]string{"Random-Boolean-Device, Random-Integer-Device"}, nil) mockAppService.On("SetFunctionsPipeline", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(nil) + mockAppService.On("LoadCustomConfig", mock.Anything, mock.Anything, mock.Anything). + Return(nil) + mockAppService.On("ListenForCustomConfigChanges", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil) mockAppService.On("MakeItRun").Return(nil) return mockAppService, true } + app := myApp{} expected := 0 - actual := CreateAndRunService("TestKey", mockFactory) + actual := app.CreateAndRunAppService("TestKey", mockFactory) assert.Equal(t, expected, actual) } @@ -55,8 +60,9 @@ func TestCreateAndRunService_NewService_Failed(t *testing.T) { mockFactory := func(_ string) (interfaces.ApplicationService, bool) { return nil, false } + app := myApp{} expected := -1 - actual := CreateAndRunService("TestKey", mockFactory) + actual := app.CreateAndRunAppService("TestKey", mockFactory) assert.Equal(t, expected, actual) } @@ -70,8 +76,9 @@ func TestCreateAndRunService_GetAppSettingStrings_Failed(t *testing.T) { return mockAppService, true } + app := myApp{} expected := -1 - actual := CreateAndRunService("TestKey", mockFactory) + actual := app.CreateAndRunAppService("TestKey", mockFactory) assert.Equal(t, expected, actual) } @@ -81,14 +88,19 @@ func TestCreateAndRunService_SetFunctionsPipeline_Failed(t *testing.T) { mockAppService.On("LoggingClient").Return(logger.NewMockClient()) mockAppService.On("GetAppSettingStrings", "DeviceNames"). Return([]string{"Random-Boolean-Device, Random-Integer-Device"}, nil) + mockAppService.On("LoadCustomConfig", mock.Anything, mock.Anything, mock.Anything). + Return(nil) + mockAppService.On("ListenForCustomConfigChanges", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil) mockAppService.On("SetFunctionsPipeline", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(fmt.Errorf("Failed")) return mockAppService, true } + app := myApp{} expected := -1 - actual := CreateAndRunService("TestKey", mockFactory) + actual := app.CreateAndRunAppService("TestKey", mockFactory) assert.Equal(t, expected, actual) } @@ -98,6 +110,10 @@ func TestCreateAndRunService_MakeItRun_Failed(t *testing.T) { mockAppService.On("LoggingClient").Return(logger.NewMockClient()) mockAppService.On("GetAppSettingStrings", "DeviceNames"). Return([]string{"Random-Boolean-Device, Random-Integer-Device"}, nil) + mockAppService.On("LoadCustomConfig", mock.Anything, mock.Anything, mock.Anything). + Return(nil) + mockAppService.On("ListenForCustomConfigChanges", mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil) mockAppService.On("SetFunctionsPipeline", mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(nil) mockAppService.On("MakeItRun").Return(fmt.Errorf("Failed")) @@ -105,7 +121,8 @@ func TestCreateAndRunService_MakeItRun_Failed(t *testing.T) { return mockAppService, true } + app := myApp{} expected := -1 - actual := CreateAndRunService("TestKey", mockFactory) + actual := app.CreateAndRunAppService("TestKey", mockFactory) assert.Equal(t, expected, actual) } diff --git a/app-service-template/res/configuration.toml b/app-service-template/res/configuration.toml index 4e4364d89..de0f44f14 100644 --- a/app-service-template/res/configuration.toml +++ b/app-service-template/res/configuration.toml @@ -57,7 +57,7 @@ RetryWaitPeriod = "1s" AuthType = 'X-Vault-Token' [Clients] - [Clients.CoreData] + [Clients.edgex-core-data] Protocol = 'http' Host = 'localhost' Port = 48080 @@ -102,10 +102,21 @@ PublishTopic="event-xml" #TODO: remove if service is NOT publishing back to the # ConnectTimeout = "30" # Seconds # SkipCertVerify = "false" - -[ApplicationSettings] -# TODO: Add custom settings needed by your app service +# TODO: Add custom settings needed by your app service or remove if you don't have any settings. # This can be any Key/Value pair you need. # For more details see: https://docs.edgexfoundry.org/1.3/microservices/application/GeneralAppServiceConfig/#application-settings # Example that works with devices from the Virtual Device service: +[ApplicationSettings] DeviceNames = "Random-Boolean-Device, Random-Integer-Device, Random-UnsignedInteger-Device, Random-Float-Device, Random-Binary-Device" + +# TODO: Replace this section with your actual structured custom configuration section +# or remove if you don't have a need for structured custom configuration +# This can be any structure you need, but it can not contain slices. Use a maps instead of slices. +# For more details see: https://docs.edgexfoundry.org/2.0/microservices/application/GeneralAppServiceConfig/#custom-configuration +[AppCustom] +ResourceNames = "Boolean, Int32, Uint32, Float32, Binary" +SomeValue = 123 + [AppCustom.SomeService] + Host = "localhost" + Port = 9080 + Protocol = "http" \ No newline at end of file diff --git a/go.mod b/go.mod index fa10f93d8..8198a8c66 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.15 require ( bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690 github.com/diegoholiveira/jsonlogic v1.0.1-0.20200220175622-ab7989be08b9 - github.com/eclipse/paho.mqtt.golang v1.2.0 - github.com/edgexfoundry/go-mod-bootstrap/v2 v2.0.0-dev.14 + github.com/eclipse/paho.mqtt.golang v1.3.2 + github.com/edgexfoundry/go-mod-bootstrap/v2 v2.0.0-dev.18 github.com/edgexfoundry/go-mod-core-contracts/v2 v2.0.0-dev.54 - github.com/edgexfoundry/go-mod-messaging/v2 v2.0.0-dev.3 + github.com/edgexfoundry/go-mod-messaging/v2 v2.0.0-dev.6 github.com/edgexfoundry/go-mod-registry/v2 v2.0.0-dev.3 github.com/fxamacker/cbor/v2 v2.2.0 github.com/gomodule/redigo v2.0.0+incompatible @@ -16,3 +16,4 @@ require ( github.com/gorilla/mux v1.8.0 github.com/stretchr/testify v1.7.0 ) + diff --git a/internal/app/service.go b/internal/app/service.go index 936d61c3e..d31587a7c 100644 --- a/internal/app/service.go +++ b/internal/app/service.go @@ -28,10 +28,14 @@ import ( "sync" "syscall" - "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/secret" + "github.com/edgexfoundry/go-mod-core-contracts/v2/clients" "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/command" "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/coredata" + "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger" "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/notifications" + "github.com/edgexfoundry/go-mod-core-contracts/v2/models" + "github.com/edgexfoundry/go-mod-messaging/v2/messaging" + "github.com/edgexfoundry/go-mod-messaging/v2/pkg/types" "github.com/edgexfoundry/go-mod-registry/v2/registry" "github.com/edgexfoundry/app-functions-sdk-go/v2/internal" @@ -49,13 +53,10 @@ import ( "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/flags" bootstrapHandlers "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/handlers" bootstrapInterfaces "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/interfaces" + "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/secret" "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/startup" "github.com/edgexfoundry/go-mod-bootstrap/v2/di" - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients" - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger" - "github.com/edgexfoundry/go-mod-core-contracts/v2/models" - "github.com/edgexfoundry/go-mod-messaging/v2/messaging" - "github.com/edgexfoundry/go-mod-messaging/v2/pkg/types" + "github.com/gorilla/mux" ) @@ -94,6 +95,8 @@ type Service struct { customTriggerFactories map[string]func(sdk *Service) (interfaces.Trigger, error) profileSuffixPlaceholder string commandLine commandLineFlags + flags *flags.Default + configProcessor *config.Processor } type commandLineFlags struct { @@ -346,18 +349,18 @@ func (svc *Service) Initialize() error { " If the name provided contains the text ``, this text will be replaced with\n" + " the name of the profile used." - sdkFlags := flags.NewWithUsage(additionalUsage) - sdkFlags.FlagSet.BoolVar(&svc.commandLine.skipVersionCheck, "skipVersionCheck", false, "") - sdkFlags.FlagSet.BoolVar(&svc.commandLine.skipVersionCheck, "s", false, "") - sdkFlags.FlagSet.StringVar(&svc.commandLine.serviceKeyOverride, "serviceKey", "", "") - sdkFlags.FlagSet.StringVar(&svc.commandLine.serviceKeyOverride, "sk", "", "") + svc.flags = flags.NewWithUsage(additionalUsage) + svc.flags.FlagSet.BoolVar(&svc.commandLine.skipVersionCheck, "skipVersionCheck", false, "") + svc.flags.FlagSet.BoolVar(&svc.commandLine.skipVersionCheck, "s", false, "") + svc.flags.FlagSet.StringVar(&svc.commandLine.serviceKeyOverride, "serviceKey", "", "") + svc.flags.FlagSet.StringVar(&svc.commandLine.serviceKeyOverride, "sk", "", "") - sdkFlags.Parse(os.Args[1:]) + svc.flags.Parse(os.Args[1:]) // Temporarily setup logging to STDOUT so the client can be used before bootstrapping is completed svc.lc = logger.NewClient(svc.serviceKey, models.InfoLog) - svc.setServiceKey(sdkFlags.Profile()) + svc.setServiceKey(svc.flags.Profile()) svc.lc.Info(fmt.Sprintf("Starting %s %s ", svc.serviceKey, internal.ApplicationVersion)) @@ -378,7 +381,7 @@ func (svc *Service) Initialize() error { svc.ctx.appWg, deferred, successful = bootstrap.RunAndReturnWaitGroup( svc.ctx.appCtx, svc.ctx.appCancelCtx, - sdkFlags, + svc.flags, svc.serviceKey, internal.ConfigRegistryStem, svc.config, @@ -430,6 +433,32 @@ func (svc *Service) Initialize() error { return nil } +// LoadCustomConfig uses the Config Processor from go-mod-bootstrap to attempt to load service's +// custom configuration. It uses the same command line flags to process the custom config in the same manner +// as the standard configuration. +func (svc *Service) LoadCustomConfig(customConfig interfaces.UpdatableConfig, sectionName string) error { + if svc.configProcessor == nil { + svc.configProcessor = config.NewProcessorForCustomConfig(svc.lc, svc.flags, svc.ctx.appCtx, svc.ctx.appWg, svc.dic) + } + return svc.configProcessor.LoadCustomConfigSection(customConfig, sectionName) +} + +// ListenForCustomConfigChanges uses the Config Processor from go-mod-bootstrap to attempt to listen for +// changes to the specified custom configuration section. LoadCustomConfig must be called previously so that +// the instance of svc.configProcessor has already been set. +func (svc *Service) ListenForCustomConfigChanges( + configToWatch interfaces.WritableConfig, + sectionName string, + writableChanged chan bool) error { + if svc.configProcessor == nil { + return fmt.Errorf( + "custom configuration must be loaded before '%s' section can be watched for changes", + sectionName) + } + + return svc.configProcessor.ListenForCustomConfigChanges(configToWatch, sectionName, writableChanged) +} + // GetSecret retrieves secret data from the secret store at the specified path. func (svc *Service) GetSecret(path string, keys ...string) (map[string]string, error) { secretProvider := bootstrapContainer.SecretProviderFrom(svc.dic.Get) diff --git a/pkg/interfaces/mocks/ApplicationService.go b/pkg/interfaces/mocks/ApplicationService.go index 7f9f8a6f0..57981bb1f 100644 --- a/pkg/interfaces/mocks/ApplicationService.go +++ b/pkg/interfaces/mocks/ApplicationService.go @@ -162,6 +162,20 @@ func (_m *ApplicationService) GetSecret(path string, keys ...string) (map[string return r0, r1 } +// ListenForCustomConfigChanges provides a mock function with given fields: configToWatch, sectionName, writableChanged +func (_m *ApplicationService) ListenForCustomConfigChanges(configToWatch interfaces.WritableConfig, sectionName string, writableChanged chan bool) error { + ret := _m.Called(configToWatch, sectionName, writableChanged) + + var r0 error + if rf, ok := ret.Get(0).(func(interfaces.WritableConfig, string, chan bool) error); ok { + r0 = rf(configToWatch, sectionName, writableChanged) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // LoadConfigurablePipeline provides a mock function with given fields: func (_m *ApplicationService) LoadConfigurablePipeline() ([]func(interfaces.AppFunctionContext, interface{}) (bool, interface{}), error) { ret := _m.Called() @@ -185,6 +199,20 @@ func (_m *ApplicationService) LoadConfigurablePipeline() ([]func(interfaces.AppF return r0, r1 } +// LoadCustomConfig provides a mock function with given fields: config, sectionName +func (_m *ApplicationService) LoadCustomConfig(config interfaces.UpdatableConfig, sectionName string) error { + ret := _m.Called(config, sectionName) + + var r0 error + if rf, ok := ret.Get(0).(func(interfaces.UpdatableConfig, string) error); ok { + r0 = rf(config, sectionName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // LoggingClient provides a mock function with given fields: func (_m *ApplicationService) LoggingClient() logger.LoggingClient { ret := _m.Called() diff --git a/pkg/interfaces/service.go b/pkg/interfaces/service.go index 475533111..6927e9403 100644 --- a/pkg/interfaces/service.go +++ b/pkg/interfaces/service.go @@ -18,6 +18,7 @@ package interfaces import ( "net/http" + bootstrapInterfaces "github.com/edgexfoundry/go-mod-bootstrap/v2/bootstrap/interfaces" "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/command" "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/coredata" "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger" @@ -40,6 +41,24 @@ const ( ProfileSuffixPlaceholder = "" ) +// UpdatableConfig interface allows services to have custom configuration populated from configuration stored +// in the Configuration Provider (aka Consul). Services using custom configuration must implement this interface +// on their custom configuration, even if they do not use Configuration Provider. If they do not use the +// Configuration Provider they can have dummy implementation of this interface. +// This wraps the actual interface from go-mod-bootstrap so app service code doesn't have to have the additional +// direct import of go-mod-bootstrap. +type UpdatableConfig interface { + bootstrapInterfaces.UpdatableConfig +} + +// WritableConfig interface allows a service to listen for changes to a section of its custom configuration. +// Services not using this feature don't have to implement this interface on their custom configuration. +// This wraps the actual interface from go-mod-bootstrap so app service code doesn't have to have the additional +// direct import of go-mod-bootstrap. +type WritableConfig interface { + bootstrapInterfaces.WritableConfig +} + // ApplicationService defines the interface for an edgex Application Service type ApplicationService interface { // AddRoute a custom REST route to the application service's internal webserver @@ -99,4 +118,14 @@ type ApplicationService interface { // invalid function name, etc. // Only useful if pipeline from configuration is always defined in configuration as in App Service Configurable. LoadConfigurablePipeline() ([]AppFunction, error) + // LoadCustomConfig loads the service's custom configuration from local file or the Configuration Provider (if enabled) + // Configuration Provider will also be seeded with the custom configuration if service is using the Configuration Provider. + // UpdateFromRaw interface will be called on the custom configuration when the configuration is loaded from the + // Configuration Provider. + LoadCustomConfig(config UpdatableConfig, sectionName string) error + // ListenForCustomConfigChanges starts a listener on the Configuration Provider for changes to the specified + // section of the custom configuration. When changes are received from the Configuration Provider the + // UpdateWritableFromRaw interface will be called on the custom configuration to apply the updates and then signal + // that the changes occurred via writableChanged. + ListenForCustomConfigChanges(configToWatch WritableConfig, sectionName string, writableChanged chan bool) error }