/go-mod-configuration

Go module providing abstract configuration provider with Consul implementation | Owner: Core Working Group

Primary LanguageGoApache License 2.0Apache-2.0

go-mod-configuration

Build Status Code Coverage Go Report Card GitHub Latest Dev Tag) GitHub Latest Stable Tag) GitHub License GitHub go.mod Go version GitHub Pull Requests GitHub Contributors GitHub Committers GitHub Commit Activity

Configuration client library for use by Go implementation of EdgeX micro services. This project contains the abstract Configuration API and an implementation for Consul. The API initializes a connection to the Configuration service and push/pull configuration values to/from the Configuration service.

What is this repository for?

  • Initialize connection to a Configuration service
  • Push a service's configuration in to the Configuration
  • Pull service's configuration from the Configuration service into its configuration struct
  • Listen for configuration updates

Installation

  • Make sure you have Go Modules enabled, i.e. have an initialized go.mod file
  • If your code is in your GOPATH then make sure GO111MODULE=on is set
  • Run go get github.com/edgexfoundry/go-mod-configuration
    • This will add the go-mod-configuration to the go.mod file and download it into the module cache

How to Use

This library is used by Go programs for interacting with the Configuration service (i.e. Consul) and requires that a Configuration service be running somewhere that the Configuration Client can connect. The types.ServiceConfig struct is used to specify the service implementation details :

type ServiceConfig struct {
	Protocol string
	Host string
	Port int
	Type string
	BasePath string
	AccessToken string
}

The following code snippets demonstrate how a service uses this Configuration module to store and load configuration, listen to for configuration updates.

This code snippet shows how to connect to the Configuration service, store and load the service's configuration from the Configuration service.

func initializeConfiguration(useConfigService bool, useProfile string) (*ConfigurationStruct, error) {
	configuration := &ConfigurationStruct{}
	err := config.LoadFromFile(useProfile, configuration)
	if err != nil {
		return nil, err
	}

    if useConfigService {
        serviceConfig := types.Config{
            Host:            conf.Configuration.Host,
            Port:            conf.Configuration.Port,
            Type:            conf.Configuration.Type,
            BasePath:        internal.ConfigStem + internal.MyServiceKey,
            AccessToken:     <AccessTokenLoadedFromSecretFile>,
        }

        ConfigClient, err = configuration.NewConfigurationClient(serviceConfig)
    	if err != nil {
    		return fmt.Errorf("connection to Configuration service could not be made: %v", err.Error())
    	}


		hasConfig, err := ConfigClient.HasConfiguration()
		if hasConfig {
            // Get the service's configuration from the Configuration service
            rawConfig, err := ConfigClient.GetConfiguration(configuration)
            if err != nil {
                return fmt.Errorf("could not get configuration from Configuration: %v", err.Error())
            }

            actual, ok := rawConfig.(*ConfigurationStruct)
            if !ok {
                return fmt.Errorf("configuration from Configuration failed type check")
            }

            *configuration = actual
        } else {
            err = ConfigClient.PutConfiguration(configuration, true)
			if err != nil {
				return fmt.Errorf("could not push configuration into Configuration service: %v", err)
			}
        }
        
        // Run as go func so doesn't block
        go listenForConfigChanges()
    }

This code snippet shows how to listen for configuration changes from the Configuration service after connecting above.

func listenForConfigChanges() {
	if ConfigClient == nil {
		LoggingClient.Error("listenForConfigChanges() configuration client not set")
		return
	}

	ConfigClient.WatchForChanges(updateChannel, errChannel, &WritableInfo{}, internal.WritableKey)

	signalChan := make(chan os.Signal)
	signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)

	for {
		select {
		case <- signalChan:
			// Quietly and gracefully stop when SIGINT/SIGTERM received
			return

		case ex := <-errChannel:
			LoggingClient.Error(ex.Error())

		case raw, ok := <-updateChannel:
			if !ok {
				return
			}

			actual, ok := raw.(*WritableInfo)
			if !ok {
				LoggingClient.Error("listenForConfigChanges() type check failed")
				return
			}

			Configuration.Writable = *actual

			LoggingClient.Info("Writeable configuration has been updated")
			LoggingClient.SetLogLevel(Configuration.Writable.LogLevel)
		}
	}
}