Skip to content

Commit

Permalink
feat: Add Self seeding, env var overrides, cmd-line options per ADR 0…
Browse files Browse the repository at this point in the history
…005-Service-Self-Config.md

closes #14

Signed-off-by: lenny <[email protected]>
  • Loading branch information
lenny committed Mar 21, 2020
1 parent f5ed8c2 commit 6cd33d4
Show file tree
Hide file tree
Showing 12 changed files with 777 additions and 121 deletions.
107 changes: 80 additions & 27 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ import (
"fmt"
"os"
"os/signal"
"reflect"
"sync"
"syscall"

"github.com/edgexfoundry/go-mod-configuration/configuration"
"github.com/edgexfoundry/go-mod-core-contracts/clients/logger"
"github.com/edgexfoundry/go-mod-registry/registry"

"github.com/edgexfoundry/go-mod-bootstrap/bootstrap/config"
"github.com/edgexfoundry/go-mod-bootstrap/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/bootstrap/flags"
Expand All @@ -30,12 +35,6 @@ import (
"github.com/edgexfoundry/go-mod-bootstrap/bootstrap/registration"
"github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup"
"github.com/edgexfoundry/go-mod-bootstrap/di"

"github.com/edgexfoundry/go-mod-configuration/configuration"

"github.com/edgexfoundry/go-mod-core-contracts/clients/logger"

"github.com/edgexfoundry/go-mod-registry/registry"
)

// Deferred defines the signature of a function returned by RunAndReturnWaitGroup that should be executed via defer.
Expand Down Expand Up @@ -93,26 +92,78 @@ func RunAndReturnWaitGroup(
var wg sync.WaitGroup
deferred := func() {}

lc := logging.FactoryToStdout(serviceKey)

// Enforce serviceConfig (which is an interface) is a pointer so we can dereference it later with confidence when required.
if reflect.TypeOf(serviceConfig).Kind() != reflect.Ptr {
fatalError(fmt.Errorf("serviceConfig parameter must be a pointer to the service's configuration struct"), lc)
}

translateInterruptToCancel(ctx, &wg, cancel)

configFileName := commonFlags.ConfigFileName()
lc := logging.FactoryToStdout(serviceKey)

// Create new ProviderInfo and initialize it from command-line flag or Environment variable
configProviderInfo, err := config.NewProviderInfo(lc, commonFlags.ConfigProviderUrl())
if err != nil {
fatalError(err, lc)
// TODO: remove this check once -r/-registry is back to a bool in release v2.0.0
if len(commonFlags.ConfigProviderUrl()) > 0 && len(commonFlags.RegistryUrl()) > 0 {
fatalError(fmt.Errorf("use of -cp/-configProvider with -r/-registry=<url> not premitted"), lc)
}

// override file-based configuration with environment variables.
bootstrapConfig := serviceConfig.GetBootstrap()
startupInfo := config.OverrideStartupInfoFromEnvironment(lc, bootstrapConfig.Startup)
environment := config.NewEnvironment()
startupInfo := environment.OverrideStartupInfo(lc, bootstrapConfig.Startup)

// Update the startup timer to reflect whatever configuration read, if anything available.
startupTimer.UpdateTimer(startupInfo.Duration, startupInfo.Interval)

// Local configuration must be loaded first in case need registry config info and/or
// need to push it to the Configuration Provider.
err = config.LoadFromFile(
lc,
commonFlags.ConfigDirectory(),
commonFlags.Profile(),
configFileName,
serviceConfig,
)
if err != nil {
fatalError(err, lc)
}

// Environment variable overrides have precedence over all others,
// so make sure they are applied before config is used for anything.
overrideCount, err := environment.OverrideConfiguration(lc, serviceConfig)
if err != nil {
fatalError(err, lc)
}

configProviderUrl := commonFlags.ConfigProviderUrl()
// TODO: remove this check once -r/-registry is back to a bool and only enable registry usage in release v2.0.0
// For backwards compatibility with Fuji device and app services that use just -r/-registry for both registry and config
if len(configProviderUrl) == 0 && commonFlags.UseRegistry() {
if len(commonFlags.RegistryUrl()) > 0 {
configProviderUrl = commonFlags.RegistryUrl()
lc.Info("Config Provider URL created from -r/-registry=<url> flag")
} else {
// Have to use the Registry config for Configuration provider
registryConfig := serviceConfig.GetBootstrap().Registry
configProviderUrl = fmt.Sprintf("%s.http://%s:%d", registryConfig.Type, registryConfig.Host, registryConfig.Port)
lc.Info("Config Provider URL created from Registry configuration")
}
}

// Create new ProviderInfo and initialize it from command-line flag or Environment variables
configProviderInfo, err := config.NewProviderInfo(lc, environment, configProviderUrl)
if err != nil {
fatalError(err, lc)
}

switch configProviderInfo.UseProvider() {
case true:
lc.Info(fmt.Sprintf(
"Using Configuration provider (%s) from: %s",
configProviderInfo.ServiceConfig().Type,
configProviderInfo.ServiceConfig().GetUrl()))

var configClient configuration.Client

// set up configClient; use it to load configuration from provider.
Expand All @@ -122,36 +173,38 @@ func RunAndReturnWaitGroup(
configProviderInfo.ServiceConfig(),
serviceConfig,
configStem,
commonFlags.OverwriteConfig(),
lc,
serviceKey,
environment,
overrideCount,
)
if err != nil {
fatalError(err, lc)
}

lc = logging.FactoryFromConfiguration(serviceKey, serviceConfig)
config.ListenForChanges(ctx, &wg, serviceConfig, lc, configClient, configUpdatedStream)
lc.Info(fmt.Sprintf("Loaded configuration from %s", configProviderInfo.ServiceConfig().GetUrl()))

case false:
// load configuration from file.
err = config.LoadFromFile(
lc,
commonFlags.ConfigDirectory(),
commonFlags.Profile(),
configFileName,
serviceConfig,
)
if err != nil {
fatalError(err, lc)
}
lc = logging.FactoryFromConfiguration(serviceKey, serviceConfig)
config.LogConfigInfo(lc, "Using local configuration from file", overrideCount, serviceKey)
}

var registryClient registry.Client

// setup registryClient if it is enabled
if commonFlags.UseRegistry() {
registryClient, err = registration.RegisterWithRegistry(ctx, startupTimer, serviceConfig, lc, serviceKey)
// TODO: Remove `|| config.UseRegistry()` for release V2.0.0
if commonFlags.UseRegistry() || environment.UseRegistry() {
// For backwards compatibility with Fuji Device Service, registry is a string that can contain a provider URL.
// TODO: Remove registryUrl in call below for release V2.0.0
registryClient, err = registration.RegisterWithRegistry(
ctx,
startupTimer,
serviceConfig,
commonFlags.RegistryUrl(),
environment,
lc,
serviceKey)
if err != nil {
fatalError(err, lc)
}
Expand Down
47 changes: 39 additions & 8 deletions bootstrap/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import (
"context"
"errors"
"fmt"
"reflect"
"sync"

"github.com/edgexfoundry/go-mod-bootstrap/bootstrap/interfaces"
"github.com/edgexfoundry/go-mod-bootstrap/bootstrap/startup"

"github.com/edgexfoundry/go-mod-configuration/configuration"
configTypes "github.com/edgexfoundry/go-mod-configuration/pkg/types"

"github.com/edgexfoundry/go-mod-core-contracts/clients/logger"
)

Expand All @@ -44,29 +44,55 @@ func createClient(
return configuration.NewConfigurationClient(providerConfig)
}

// UpdateFromProvider connects to the config provider, gets configuration, and updates the service's
// UpdateFromProvider connects to the configuration provider, puts or gets configuration, and updates the service's
// configuration struct.
func UpdateFromProvider(
ctx context.Context,
startupTimer startup.Timer,
providerConfig configTypes.ServiceConfig,
serviceConfig interfaces.Configuration,
configStem string,
overwriteConfig bool,
lc logger.LoggingClient,
serviceKey string) (configuration.Client, error) {
serviceKey string,
environment *Environment,
overrideCount int) (configuration.Client, error) {

var updateFromConfigProvider = func(configClient configuration.Client) error {
if !configClient.IsAlive() {
return errors.New("configuration provider is not available")
}

rawConfig, err := configClient.GetConfiguration(serviceConfig)
hasConfig, err := configClient.HasConfiguration()
if err != nil {
return fmt.Errorf("could not get configuration from Registry: %v", err.Error())
return fmt.Errorf("could not determine if Configuration provider has configuration: %s", err.Error())
}

if !serviceConfig.UpdateFromRaw(rawConfig) {
return errors.New("configuration from Registry failed type check")
if !hasConfig || overwriteConfig {
// Environment overrides already applied previously so just push to Configuration Provider
// Note that serviceConfig is a pointer, so we have to use reflection to dereference it.
err = configClient.PutConfiguration(reflect.ValueOf(serviceConfig).Elem().Interface(), true)
if err != nil {
return fmt.Errorf("could not push configuration into Configuration Provider: %s", err.Error())
}

LogConfigInfo(lc, "Configuration has been pushed to into Configuration Provider", overrideCount, serviceKey)
} else {
rawConfig, err := configClient.GetConfiguration(serviceConfig)
if err != nil {
return fmt.Errorf("could not get configuration from Configuration provider: %s", err.Error())
}

if !serviceConfig.UpdateFromRaw(rawConfig) {
return errors.New("configuration from Configuration provider failed type check")
}

overrideCount, err := environment.OverrideConfiguration(lc, serviceConfig)
if err != nil {
return err
}

LogConfigInfo(lc, "Configuration has been pulled from Configuration provider", overrideCount, serviceKey)
}

return nil
Expand All @@ -93,7 +119,12 @@ func UpdateFromProvider(
return nil, errors.New("unable to update configuration from provider in allotted time")
}

// ListenForChanges leverages the registry client's WatchForChanges() method to receive changes to and update the
// LogConfigInfo logs the config info message with number over overrides that occurred and the service key that was used.
func LogConfigInfo(lc logger.LoggingClient, message string, overrideCount int, serviceKey string) {
lc.Info(fmt.Sprintf("%s (%d environment overrides applied)", message, overrideCount), "service key", serviceKey)
}

// ListenForChanges leverages the Configuration Provider client's WatchForChanges() method to receive changes to and update the
// service's configuration struct's writable sub-struct. It's assumed the log level is universally part of the
// writable struct and this function explicitly updates the loggingClient's log level when new configuration changes
// are received.
Expand Down
Loading

0 comments on commit 6cd33d4

Please sign in to comment.