appconfig

package
v0.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 24, 2024 License: Apache-2.0 Imports: 12 Imported by: 0

README

Application Config

The appconfig package allows application properties to be defined in multiple sources including environment variables, command line, yaml file, consul and vault.

Example Usage

To use the package, include appconfig.Use() in your application.

To bind properties to a golang struct, use the func (c *config) Bind(target interface{}, prefix string) error method. The following example will bind the following yaml property into the Properties struct.

info:
  app:
    name: my_app
    description: an example application
const (
	PropertiesPrefix = "info.app"
)

type Properties struct {
	name string `json:"name"`
	description string `json:"description"`
}


//NewProperties create a Properties with default values
func NewProperties() *Properties {
	return &Properties{}
}

//BindProperties create and bind SessionProperties, with a optional prefix
func BindProperties(ctx *bootstrap.ApplicationContext) Properties {
	props := NewProperties()
	if err := ctx.Config().Bind(props, PropertiesPrefix); err != nil {
		panic(errors.Wrap(err, "failed to bind Properties"))
	}
	return *props
}

Property Source and Precedence

Properties are loaded from multiple sources in two stages. This is because in order to connect to external property sources like Vault and Consul the application needs to read their connection properties. The bootstrap stage allows these to be read in so that they can be used in the application stage.

Bootstrap Stage

In this stage, properties are loaded from the following sources. The sources are listed based on priority order from low to high. High priority sources overrides lower priority sources

  • embedded default yaml
  • bootstrap yaml
  • environment variables
  • command line
Application Stage

In the application stage, more property sources are added so the complete list becomes

  • embedded default yaml
  • bootstrap yaml
  • application yaml
  • environment variables
  • command line
  • consul and vault default name space
  • consul and vault application name space
Profiles

Application can be run using profiles to load profile specific properties. The property sources that supports profile are:

  • bootstrap yaml
  • application yaml
  • consul
  • vault

For yaml files, when application is run with profile the application will load those yaml files that have the profile name appended. i.e. bootstrap-{profile_name}.yml and application-{profile_name}.yml in addition to the default bootstrap.yml and application.yml file. The properties in the profile specific yml file will override the properties in the default file.

For consul, properties in the name space that have the profile appended will be loaded in addition to the non-profiled name space. i.e. e.g. properties in userviceconfiguration/defaultapplication,{profile-name} in addition to userviceconfiguration/defaultapplication

For vault, the properties in the profile context will be loaded in addition to those in the non-profiled context. e.g. defaultapplication/{profile-name} in addition to defaultapplication/

active profile vs additional profile

Profiles can be specified either using application.profiles.active or application.profiles.additional. To provide these two property values via command line, use active-profiles and additional-profiles (e.g. service --active-profiles a)

application.profiles.active This property gets overridden like any other property if it's defined in multiple property sources.

application.profiles.additional Unlike other properties, this property is appended if it's defined in multiple property sources.

Because each property source can add more profiles, the property loading process in both bootstrap and application stage will continue to refresh the list of property sources until there are no new property sources. i.e. when the value of these two properties stabilizes.

Requirements and Best Practices

Json Tag in Struct

We require the json tag in struct to be snake-case instead of camelCase. This is because internally all the property names from property sources are normalized to snake-case and merged together. This allows user to define their property either using snake-case or camelCase. But the developer must use snake-case to bind the resulting property to struct field, because that is the format that all properties uses internally.

Fields that Supports Binding

In some cases the property value might correspond to special types. For example, a property might represent a time duration or a slice. In this case, the struct field type needs to be able to unmarshall the property values. go-lanai provides some utility types to help with this so that developer does not need to write custom json unmarshalling code. This is a list of the available types:

  • utils.Duration
  • utils.CommaSeparatedSlice
  • utils.StringSet
  • utils.Set
  • utils.GenericSet[T]

Documentation

Index

Constants

View Source
const (
	PropertyKeyActiveProfiles       = "application.profiles.active"
	PropertyKeyAdditionalProfiles   = "application.profiles.additional"
	PropertyKeyConfigFileSearchPath = "config.file.search-path"
	PropertyKeyApplicationName      = bootstrap.PropertyKeyApplicationName
	PropertyKeyBuildInfo            = "application.build"
)

Variables

This section is empty.

Functions

func NormalizeKey

func NormalizeKey(key string, configures ...func(*Options)) string

NormalizeKey convert camelCase key to snake-case

func ProcessKeyFormat

func ProcessKeyFormat(nested map[string]interface{}, processor func(string, ...func(*Options)) string) (map[string]interface{}, error)

ProcessKeyFormat traverse given map in DFS fashion and apply given processor to each KV pair

func UnFlatten

func UnFlatten(flat map[string]interface{}, configures ...func(*UfOptions)) (nested map[string]interface{}, err error)

UnFlatten supports un-flattening keys with index like the following

my-example.url[0]=https://example.com

The indexed entries are treated like an unsorted list. The result will be a list but the order is not guaranteed to reflect the index order. A key with multiple index (a.b[0].c[0) is not supported

func UnFlattenKey

func UnFlattenKey(k string, configures ...func(*Options)) []string

func VisitEach

func VisitEach(nested map[string]interface{}, apply func(string, interface{}) error, configures ...func(*Options)) error

Types

type ApplicationConfig

type ApplicationConfig struct {
	// contains filtered or unexported fields
}

func NewApplicationConfig

func NewApplicationConfig(groups ...ProviderGroup) *ApplicationConfig

func (*ApplicationConfig) Bind

func (c *ApplicationConfig) Bind(target interface{}, prefix string) error

func (*ApplicationConfig) Each

func (c *ApplicationConfig) Each(apply func(string, interface{}) error) error

Each go through all properties and apply given function. It stops at the first error

func (*ApplicationConfig) HasProfile

func (c *ApplicationConfig) HasProfile(profile string) bool

func (*ApplicationConfig) Load

func (c *ApplicationConfig) Load(ctx context.Context, force bool) (err error)

Load will fail if place holder cannot be resolved due to circular dependency

func (*ApplicationConfig) Profiles

func (c *ApplicationConfig) Profiles() []string

func (*ApplicationConfig) ProviderGroups

func (c *ApplicationConfig) ProviderGroups() []ProviderGroup

func (*ApplicationConfig) Providers

func (c *ApplicationConfig) Providers() []Provider

func (*ApplicationConfig) Value

func (c *ApplicationConfig) Value(key string) interface{}

type BootstrapConfig

type BootstrapConfig struct {
	// contains filtered or unexported fields
}

func NewBootstrapConfig

func NewBootstrapConfig(groups ...ProviderGroup) *BootstrapConfig

func (*BootstrapConfig) Bind

func (c *BootstrapConfig) Bind(target interface{}, prefix string) error

func (*BootstrapConfig) Each

func (c *BootstrapConfig) Each(apply func(string, interface{}) error) error

Each go through all properties and apply given function. It stops at the first error

func (*BootstrapConfig) HasProfile

func (c *BootstrapConfig) HasProfile(profile string) bool

func (*BootstrapConfig) Load

func (c *BootstrapConfig) Load(ctx context.Context, force bool) (err error)

Load will fail if place holder cannot be resolved due to circular dependency

func (*BootstrapConfig) Profiles

func (c *BootstrapConfig) Profiles() []string

func (*BootstrapConfig) ProviderGroups

func (c *BootstrapConfig) ProviderGroups() []ProviderGroup

func (*BootstrapConfig) Providers

func (c *BootstrapConfig) Providers() []Provider

func (*BootstrapConfig) Value

func (c *BootstrapConfig) Value(key string) interface{}

type ConfigAccessor

type ConfigAccessor interface {
	bootstrap.ApplicationConfig
	Each(apply func(string, interface{}) error) error
	// Providers gives effective config providers
	Providers() []Provider
	Profiles() []string
	HasProfile(profile string) bool
}

type DynamicProviderGroup

type DynamicProviderGroup struct {
	Precedence        int
	ProviderKeys      []string // Provider should be sorted all time based on their provider's ordering
	ProviderLookup    map[string]Provider
	ResolvedProviders []Provider
	ProcessFunc       func(context.Context, []Provider) []Provider // ProcessFunc is invoked before setting ResolvedProviders. Last chance to change
}

DynamicProviderGroup implements ProviderGroup, and holds a sorted list of keys and their corresponding Provider. This type is typically used as embedded struct

func NewDynamicProviderGroup

func NewDynamicProviderGroup(order int) *DynamicProviderGroup

func (*DynamicProviderGroup) Order

func (g *DynamicProviderGroup) Order() int

func (*DynamicProviderGroup) Providers

func (g *DynamicProviderGroup) Providers(ctx context.Context, _ bootstrap.ApplicationConfig) (providers []Provider)

func (*DynamicProviderGroup) Reset

func (g *DynamicProviderGroup) Reset()

type Options

type Options struct {
	Delimiter string
}

Options the flatten options. By default: Delimiter = "."

type ProfileBasedProviderGroup

type ProfileBasedProviderGroup struct {
	DynamicProviderGroup
	KeyFunc    func(profile string) (key string)
	CreateFunc func(name string, order int, conf bootstrap.ApplicationConfig) Provider
}

ProfileBasedProviderGroup extends DynamicProviderGroup and implements ProviderGroup it provide base methods to determine Providers based on PropertyKeyActiveProfiles

func NewProfileBasedProviderGroup

func NewProfileBasedProviderGroup(order int) *ProfileBasedProviderGroup

func (*ProfileBasedProviderGroup) Providers

func (g *ProfileBasedProviderGroup) Providers(ctx context.Context, conf bootstrap.ApplicationConfig) (providers []Provider)

type Provider

type Provider interface {
	order.Ordered

	// Name is unique name of given provider, it also used as primary key in any mapping
	Name() string
	// Load load settings and should be idempotent. e.g. calling it multiple times should not affect loaded settings
	Load(ctx context.Context) error
	// GetSettings returns loaded settings. might be nil if not IsLoaded returns true
	// The returned map should be un-flattened. i.e. flat.key=value should be stored as {"flat":{"key":"value"}}
	GetSettings() map[string]interface{}
	// IsLoaded should return true if Load is invoked at least once
	IsLoaded() bool
	// Reset delete loaded settings and reset IsLoaded flag
	Reset()
}

type ProviderGroup

type ProviderGroup interface {
	order.Ordered

	// Providers returns providers based on given config.
	// This method should be idempotent. e.g. calling it multiple times with same config always returns identical slice
	Providers(ctx context.Context, config bootstrap.ApplicationConfig) []Provider

	// Reset should mark all providers unloaded
	Reset()
}

ProviderGroup determines Providers based on given bootstrap.ApplicationConfig

type ProviderMeta

type ProviderMeta struct {
	Loaded     bool                   //invalid if not loaded or during load
	Settings   map[string]interface{} //storage for the settings loaded by the auth
	Precedence int                    //the precedence for which the settings will take effect.
}

ProviderMeta implements ProviderReorderer and partial ProviderMeta

func (ProviderMeta) GetSettings

func (m ProviderMeta) GetSettings() map[string]interface{}

func (ProviderMeta) IsLoaded

func (m ProviderMeta) IsLoaded() bool

func (ProviderMeta) Order

func (m ProviderMeta) Order() int

func (*ProviderMeta) Reorder

func (m *ProviderMeta) Reorder(order int)

func (*ProviderMeta) Reset

func (m *ProviderMeta) Reset()

type ProviderReorderer

type ProviderReorderer interface {
	// Reorder set order
	Reorder(int)
}

type StaticProviderGroup

type StaticProviderGroup struct {
	Precedence      int
	StaticProviders []Provider
}

StaticProviderGroup implements ProviderGroup, and holds fixed provider list

func NewStaticProviderGroup

func NewStaticProviderGroup(order int, providers ...Provider) *StaticProviderGroup

func (StaticProviderGroup) Order

func (g StaticProviderGroup) Order() int

func (StaticProviderGroup) Providers

func (*StaticProviderGroup) Reset

func (g *StaticProviderGroup) Reset()

type UfOptions

type UfOptions struct {
	Delimiter   string
	AppendSlice bool
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL