Skip to content

Commit

Permalink
Merge pull request #176 from launchdarkly/eb/ch87406/unify-config
Browse files Browse the repository at this point in the history
(#3) move core/config to ./config, move core to internal/core, unify config types
  • Loading branch information
eli-darkly authored Sep 8, 2020
2 parents 32b3020 + f57eeb7 commit 36bd89f
Show file tree
Hide file tree
Showing 158 changed files with 418 additions and 817 deletions.
19 changes: 18 additions & 1 deletion core/config/config.go → config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"time"

ct "github.com/launchdarkly/go-configtypes"
"github.com/launchdarkly/ld-relay/v6/core/logging"
"github.com/launchdarkly/ld-relay/v6/internal/core/logging"
)

const (
Expand Down Expand Up @@ -37,6 +37,14 @@ const (

// DefaultPrometheusPort is the default value for PrometheusConfig.Port if not specified.
DefaultPrometheusPort = 8031

// AutoConfigEnvironmentIDPlaceholder is a string that can appear within
// AutoConfigConfig.EnvDataStorePrefix or AutoConfigConfig.EnvDataStoreTableName to indicate that
// the environment ID should be substituted at that point.
//
// For instance, if EnvDataStorePrefix is "LD-$CID", the value of that setting for an environment
// whose ID is "12345" would be "LD-12345".
AutoConfigEnvironmentIDPlaceholder = "$CID"
)

const (
Expand Down Expand Up @@ -69,6 +77,7 @@ var DefaultLoggers = logging.MakeDefaultLoggers() //nolint:gochecknoglobals
// configuration.
type Config struct {
Main MainConfig
AutoConfig AutoConfigConfig
Events EventsConfig
Redis RedisConfig
Consul ConsulConfig
Expand Down Expand Up @@ -107,6 +116,14 @@ type MainConfig struct {
LogLevel OptLogLevel `conf:"LOG_LEVEL"`
}

// AutoConfigConfig contains configuration parameters for the auto-configuration feature.
type AutoConfigConfig struct {
Key AutoConfigKey `conf:"AUTO_CONFIG_KEY"`
EnvDatastorePrefix string `conf:"ENV_DATASTORE_PREFIX"`
EnvDatastoreTableName string `conf:"ENV_DATASTORE_TABLE_NAME"`
EnvAllowedOrigin ct.OptStringList `conf:"ENV_ALLOWED_ORIGIN"`
}

// EventsConfig contains configuration parameters for proxying events.
//
// Since configuration options can be set either programmatically, or from a file, or from environment
Expand Down
16 changes: 16 additions & 0 deletions core/config/config_field_types.go → config/config_field_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type MobileKey string
// LaunchDarkly environment.
type EnvironmentID string

// AutoConfigKey is a type tag to indicate when a string is used as an auto-configuration key.
type AutoConfigKey string

// SDKCredential is implemented by types that represent an SDK authorization credential (SDKKey, etc.).
type SDKCredential interface {
// GetAuthorizationHeaderValue returns the value that should be passed in an HTTP Authorization header
Expand All @@ -53,6 +56,13 @@ func (k EnvironmentID) GetAuthorizationHeaderValue() string {
return ""
}

// GetAuthorizationHeaderValue for AutoConfigKey returns the same string, since these keys are passed in
// the Authorization header. Note that unlike the other kinds of authorization keys, this one is never
// present in an incoming request; it is only used in requests from Relay to LaunchDarkly.
func (k AutoConfigKey) GetAuthorizationHeaderValue() string {
return string(k)
}

// UnmarshalText allows the SDKKey type to be set from environment variables.
func (k *SDKKey) UnmarshalText(data []byte) error {
*k = SDKKey(string(data))
Expand All @@ -71,6 +81,12 @@ func (k *EnvironmentID) UnmarshalText(data []byte) error {
return nil
}

// UnmarshalText allows the AutoConfigKey type to be set from environment variables.
func (k *AutoConfigKey) UnmarshalText(data []byte) error {
*k = AutoConfigKey(string(data))
return nil
}

// OptLogLevel represents an optional log level parameter. It must match one of the level names "debug",
// "info", "warn", or "error" (case-insensitive).
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func TestSDKCredential(t *testing.T) {
assert.Equal(t, "123", SDKKey("123").GetAuthorizationHeaderValue())
assert.Equal(t, "456", MobileKey("456").GetAuthorizationHeaderValue())
assert.Equal(t, "", EnvironmentID("123").GetAuthorizationHeaderValue())
assert.Equal(t, "123", AutoConfigKey("123").GetAuthorizationHeaderValue())
}

func TestOptLogLevel(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions core/config/config_from_env.go → config/config_from_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func LoadConfigFromEnvironmentBase(c *Config, loggers ldlog.Loggers) ct.Validati

reader.ReadStruct(&c.Main, false)

reader.ReadStruct(&c.AutoConfig, false)

reader.ReadStruct(&c.Events, false)
rejectObsoleteVariableName("EVENTS_SAMPLING_INTERVAL", "", reader)

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
14 changes: 14 additions & 0 deletions core/config/config_validation.go → config/config_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import (
"gopkg.in/launchdarkly/go-sdk-common.v2/ldlog"
)

var (
errAutoConfPropertiesWithNoKey = errors.New("must specify auto-configuration key if other auto-configuration properties are set")
errAutoConfWithEnvironments = errors.New("cannot configure specific environments if auto-configuration is enabled")
)

func errEnvironmentWithNoSDKKey(envName string) error {
return fmt.Errorf("SDK key is required for environment %q", envName)
}
Expand All @@ -34,6 +39,15 @@ func ValidateConfig(c *Config, loggers ldlog.Loggers) error {
result.AddError(nil, errors.New("TLS cert and key are required if TLS is enabled"))
}

if c.AutoConfig.Key == "" {
if c.AutoConfig.EnvDatastorePrefix != "" || c.AutoConfig.EnvDatastoreTableName != "" ||
len(c.AutoConfig.EnvAllowedOrigin.Values()) != 0 {
result.AddError(nil, errAutoConfPropertiesWithNoKey)
}
} else if len(c.Environment) != 0 {
result.AddError(nil, errAutoConfWithEnvironments)
}

for envName, envConfig := range c.Environment {
if envConfig.SDKKey == "" {
result.AddError(nil, errEnvironmentWithNoSDKKey(envName))
Expand Down
File renamed without changes.
88 changes: 88 additions & 0 deletions core/config/test_data_test.go → config/test_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func newOptURLAbsoluteMustBeValid(urlString string) ct.OptURLAbsolute {
func makeValidConfigs() []testDataValidConfig {
return []testDataValidConfig{
makeValidConfigAllBaseProperties(),
makeValidConfigAutoConfig(),
makeValidConfigRedisMinimal(),
makeValidConfigRedisAll(),
makeValidConfigRedisURL(),
Expand All @@ -68,6 +69,10 @@ func makeInvalidConfigs() []testDataInvalidConfig {
makeInvalidConfigTLSWithNoCert(),
makeInvalidConfigTLSWithNoKey(),
makeInvalidConfigTLSVersion(),
makeInvalidConfigAutoConfKeyWithEnvironments(),
makeInvalidConfigAutoConfAllowedOriginWithNoKey(),
makeInvalidConfigAutoConfPrefixWithNoKey(),
makeInvalidConfigAutoConfTableNameWithNoKey(),
makeInvalidConfigRedisInvalidHostname(),
makeInvalidConfigRedisInvalidDockerPort(),
makeInvalidConfigRedisConflictingParams(),
Expand Down Expand Up @@ -206,6 +211,33 @@ TTL = 5m
return c
}

func makeValidConfigAutoConfig() testDataValidConfig {
c := testDataValidConfig{name: "auto-config properties"}
c.makeConfig = func(c *Config) {
c.AutoConfig = AutoConfigConfig{
Key: AutoConfigKey("autokey"),
EnvDatastorePrefix: "prefix-$CID",
EnvDatastoreTableName: "table-$CID",
EnvAllowedOrigin: ct.NewOptStringList([]string{"http://first", "http://second"}),
}
}
c.envVars = map[string]string{
"AUTO_CONFIG_KEY": "autokey",
"ENV_DATASTORE_PREFIX": "prefix-$CID",
"ENV_DATASTORE_TABLE_NAME": "table-$CID",
"ENV_ALLOWED_ORIGIN": "http://first,http://second",
}
c.fileContent = `
[AutoConfig]
Key = autokey
EnvDatastorePrefix = prefix-$CID
EnvDatastoreTableName = table-$CID
EnvAllowedOrigin = http://first
EnvAllowedOrigin = http://second
`
return c
}

func makeValidConfigRedisMinimal() testDataValidConfig {
c := testDataValidConfig{name: "Redis - minimal parameters"}
c.makeConfig = func(c *Config) {
Expand Down Expand Up @@ -603,6 +635,62 @@ TLSMinVersion = x
return c
}

func makeInvalidConfigAutoConfKeyWithEnvironments() testDataInvalidConfig {
c := testDataInvalidConfig{name: "auto-conf key with environments"}
c.envVarsError = errAutoConfWithEnvironments.Error()
c.envVars = map[string]string{
"AUTO_CONFIG_KEY": "autokey",
"LD_ENV_envname": "sdk-key",
}
c.fileContent = `
[AutoConfig]
Key = autokey
[Environment "envname"]
SDKKey = sdk-key
`
return c
}

func makeInvalidConfigAutoConfAllowedOriginWithNoKey() testDataInvalidConfig {
c := testDataInvalidConfig{name: "auto-conf allowed origin with no key"}
c.envVarsError = errAutoConfPropertiesWithNoKey.Error()
c.envVars = map[string]string{
"ENV_ALLOWED_ORIGIN": "http://origin",
}
c.fileContent = `
[AutoConfig]
EnvAllowedOrigin = http://origin
`
return c
}

func makeInvalidConfigAutoConfPrefixWithNoKey() testDataInvalidConfig {
c := testDataInvalidConfig{name: "auto-conf prefix with no key"}
c.envVarsError = errAutoConfPropertiesWithNoKey.Error()
c.envVars = map[string]string{
"ENV_DATASTORE_PREFIX": "prefix",
}
c.fileContent = `
[AutoConfig]
EnvDatastorePrefix = prefix
`
return c
}

func makeInvalidConfigAutoConfTableNameWithNoKey() testDataInvalidConfig {
c := testDataInvalidConfig{name: "auto-conf table name with no key"}
c.envVarsError = errAutoConfPropertiesWithNoKey.Error()
c.envVars = map[string]string{
"ENV_DATASTORE_TABLE_NAME": "table",
}
c.fileContent = `
[AutoConfig]
EnvDatastoreTableName = table
`
return c
}

func makeInvalidConfigRedisInvalidHostname() testDataInvalidConfig {
c := testDataInvalidConfig{name: "Redis - invalid hostname"}
c.envVarsError = "invalid Redis hostname"
Expand Down
32 changes: 21 additions & 11 deletions docs/in-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ Here is an example of how you might run the Relay Proxy endpoints inside your we

```go
import (
"github.com/launchdarkly/ld-relay/v6/core/config"
"github.com/gorilla/mux"
"github.com/launchdarkly/ld-relay/v6/config"
"github.com/launchdarkly/ld-relay/v6/relay"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldlog"
)

func createRelayConfig() config.Config {
cfg := config.DefaultConfig
if err := config.LoadConfigFile(&cfg, "path/to/my.config"); err != nil {
var cfg config.Config
if err := config.LoadConfigFile(&cfg, "path/to/my.config", ldlog.NewDefaultLoggers()); err != nil {
log.Fatalf("Error loading config file: %s", err)
}
return cfg
}

r, err := relay.NewRelay(createRelayConfig, nil)
r, err := relay.NewRelay(createRelayConfig(), ldlog.NewDefaultLoggers(), nil)
if err != nil {
log.Fatalf("Error creating relay: %s", err)
}
Expand All @@ -29,15 +31,20 @@ router := mux.NewRouter()
router.PathPrefix("/relay").Handler(r)
```

The above example uses a configuration file. You can also pass in a `config.Config` struct that you have filled in directly:
The above example uses a configuration file. You can also pass in a `config.Config` struct that you have filled in directly. Note that some of the fields use types from `github.com/launchdarkly/go-configtypes` to enforce validation rules.

```go
import (
"github.com/launchdarkly/ld-relay/v6/config"
configtypes "github.com/launchdarkly/go-configtypes"
)

func createRelayConfig() config.Config {
cfg := config.DefaultConfig
cfg.Main.Port = 5000
var cfg config.Config
cfg.Main.Port, _ = configtypes.NewOptIntGreaterThanZero(5000)
cfg.Environment = map[string]*config.EnvConfig{
"Spree Project Production": &config.EnvConfig{
SDKKey: "SPREE_PROD_API_KEY",
SDKKey: config.SDKKey("SPREE_PROD_API_KEY"),
},
}
return cfg
Expand All @@ -47,9 +54,12 @@ func createRelayConfig() config.Config {
Alternatively, you can parse the configuration from a string that is in the same format as the configuration file, using the same `gcfg` package that ld-relay uses:

```go
import "github.com/launchdarkly/gcfg"
import (
"github.com/launchdarkly/ld-relay/v6/config"
"github.com/go-gcfg/gcfg"
)

configString := `
var configString = `
[main]
port = 5000
Expand All @@ -58,7 +68,7 @@ sdkKey = "SPREE_PROD_API_KEY"
`

func createRelayConfig() config.Config {
cfg := config.DefaultConfig
var cfg config.Config
if err := gcfg.ReadStringInto(&cfg, configString); err != nil {
log.Fatalf("Error loading config file: %s", err)
}
Expand Down
4 changes: 2 additions & 2 deletions enterprise/autoconfig/message_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package autoconfig
import (
"time"

"github.com/launchdarkly/ld-relay/v6/core/config"
"github.com/launchdarkly/ld-relay/v6/core/relayenv"
"github.com/launchdarkly/ld-relay/v6/config"
"github.com/launchdarkly/ld-relay/v6/internal/core/relayenv"
)

// MessageHandler defines the methods that StreamManager will call when it receives messages
Expand Down
2 changes: 1 addition & 1 deletion enterprise/autoconfig/reps.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package autoconfig

import (
"github.com/launchdarkly/ld-relay/v6/core/config"
"github.com/launchdarkly/ld-relay/v6/config"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldtime"
)

Expand Down
2 changes: 1 addition & 1 deletion enterprise/autoconfig/reps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/launchdarkly/ld-relay/v6/core/config"
"github.com/launchdarkly/ld-relay/v6/config"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldtime"
)

Expand Down
11 changes: 5 additions & 6 deletions enterprise/autoconfig/stream_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import (
"time"

es "github.com/launchdarkly/eventsource"
"github.com/launchdarkly/ld-relay/v6/core/config"
"github.com/launchdarkly/ld-relay/v6/core/httpconfig"
"github.com/launchdarkly/ld-relay/v6/core/relayenv"
"github.com/launchdarkly/ld-relay/v6/enterprise/entconfig"
"github.com/launchdarkly/ld-relay/v6/config"
"github.com/launchdarkly/ld-relay/v6/internal/core/httpconfig"
"github.com/launchdarkly/ld-relay/v6/internal/core/relayenv"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldlog"
"gopkg.in/launchdarkly/go-sdk-common.v2/ldtime"
)
Expand Down Expand Up @@ -62,7 +61,7 @@ var (
// Relay Enterprise provides an implementation of the MessageHandler interface which will be called for all
// changes that it needs to know about.
type StreamManager struct {
key entconfig.AutoConfigKey
key config.AutoConfigKey
uri string
handler MessageHandler
lastKnownEnvs map[config.EnvironmentID]EnvironmentRep
Expand All @@ -82,7 +81,7 @@ type expiredKey struct {

// NewStreamManager creates a StreamManager, but does not start the connection.
func NewStreamManager(
key entconfig.AutoConfigKey,
key config.AutoConfigKey,
streamURI string,
handler MessageHandler,
httpConfig httpconfig.HTTPConfig,
Expand Down
4 changes: 2 additions & 2 deletions enterprise/autoconfig/stream_manager_events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
"github.com/stretchr/testify/require"

"github.com/launchdarkly/go-test-helpers/v2/httphelpers"
"github.com/launchdarkly/ld-relay/v6/core/config"
"github.com/launchdarkly/ld-relay/v6/core/relayenv"
"github.com/launchdarkly/ld-relay/v6/config"
"github.com/launchdarkly/ld-relay/v6/internal/core/relayenv"
)

func TestMakeEnvironmentParams(t *testing.T) {
Expand Down
Loading

0 comments on commit 36bd89f

Please sign in to comment.