Skip to content

Commit

Permalink
prepare 8.0.0 release (#262)
Browse files Browse the repository at this point in the history
## [8.0.0] - 2023-10-12
### Added:
- Added support for Payload Filters, which is an EAP feature. Please
contact LaunchDarkly support for more information.
- Added support for upcoming technology migration functionality.

### Changed:
- Expanded `relayMetric` to track polling requests.

### Removed:
- `DisableInternalUsageMetrics` configuration has been removed.

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: LaunchDarklyReleaseBot <[email protected]>
Co-authored-by: Eli Bishop <[email protected]>
Co-authored-by: Moshe Good <[email protected]>
Co-authored-by: Moshe Good <[email protected]>
Co-authored-by: LaunchDarklyCI <[email protected]>
Co-authored-by: hroederld <[email protected]>
Co-authored-by: Dan Richelson <[email protected]>
Co-authored-by: Dan Richelson <[email protected]>
Co-authored-by: Ben Woskow <[email protected]>
Co-authored-by: Ben Woskow <[email protected]>
Co-authored-by: Louis Chan <[email protected]>
Co-authored-by: Louis Chan <[email protected]>
Co-authored-by: Casey Waldren <[email protected]>
Co-authored-by: Phil Z <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matthew M. Keeler <[email protected]>
Co-authored-by: Ember Stevens <[email protected]>
Co-authored-by: Molly <[email protected]>
Co-authored-by: ld-repository-standards[bot] <113625520+ld-repository-standards[bot]@users.noreply.github.com>
Co-authored-by: Kane Parkinson <[email protected]>
Co-authored-by: Ember Stevens <[email protected]>
Co-authored-by: Shalini Singh <[email protected]>
Co-authored-by: Shalini Singh <[email protected]>
  • Loading branch information
24 people authored Oct 12, 2023
1 parent 7febf95 commit 7d67cb6
Show file tree
Hide file tree
Showing 172 changed files with 4,663 additions and 1,187 deletions.
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ jobs:
test-coverage:
type: boolean
default: false

docker:
- image: <<parameters.docker-image>>
environment: &environment
Expand Down Expand Up @@ -141,7 +141,7 @@ jobs:
steps:
- run:
name: Run tests
command: make test | tee $CIRCLE_ARTIFACTS/report.txt
command: make test | tee $CIRCLE_ARTIFACTS/report.txt
- run:
name: Process test results
command: go-junit-report < $CIRCLE_ARTIFACTS/report.txt > $CIRCLE_TEST_REPORTS/junit.xml
Expand Down Expand Up @@ -230,7 +230,7 @@ jobs:
- image: <<parameters.docker-image>>
steps:
- run: go version
- run: go install github.com/launchdarkly/ld-relay/v7@latest
- run: go install github.com/launchdarkly/ld-relay/v8@latest
- run:
name: verify that executable was built
command: ls -l $GOPATH/bin/ld-relay
Expand All @@ -239,7 +239,7 @@ jobs:
parameters:
docker-image:
type: string

docker:
- image: <<parameters.docker-image>>

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ build
/dist/
.idea
.vscode
.idea/
_testservice/testservice
_testservice/*.pid
temp_ssh
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ RUN addgroup -g 1000 -S ldr-user && \

RUN apk add --no-cache \
ca-certificates \
&& apk add --upgrade libcrypto1.1 libssl1.1 \
&& apk add --upgrade libcrypto1.1 libssl1.1 git \
&& update-ca-certificates \
&& rm -rf /var/cache/apk/*

Expand Down
2 changes: 1 addition & 1 deletion _testservice/streamer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"net/http"

"github.com/launchdarkly/go-server-sdk/v6/testhelpers/ldservices"
"github.com/launchdarkly/go-server-sdk/v7/testhelpers/ldservices"
)

func streamerEndpointHandler() http.Handler {
Expand Down
50 changes: 30 additions & 20 deletions 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/v7/internal/logging"
"github.com/launchdarkly/ld-relay/v8/internal/logging"
)

const (
Expand Down Expand Up @@ -60,6 +60,10 @@ const (
// For instance, if EnvDataStorePrefix is "LD-$CID", the value of that setting for an environment
// whose ID is "12345" would be "LD-12345".
//
// If the environment is scoped to a Payload Filter, then the filter key will be concatenated as follows:
// Given: "LD-$CID", environment ID "12345" and filter key "microservice-a"
// The substituted result would be: "LD-12345.microservice-a"
//
// The same convention is used in OfflineModeConfig.
AutoConfigEnvironmentIDPlaceholder = "$CID"
)
Expand Down Expand Up @@ -106,6 +110,7 @@ type Config struct {
Consul ConsulConfig
DynamoDB DynamoDBConfig
Environment map[string]*EnvConfig
Filters map[string]*FiltersConfig
Proxy ProxyConfig

// Optional configuration for metrics integrations. Note that unlike the other fields in Config,
Expand All @@ -122,25 +127,24 @@ type Config struct {
// variables, individual fields are not documented here; instead, see the `README.md` section on
// configuration.
type MainConfig struct {
ExitOnError bool `conf:"EXIT_ON_ERROR"`
ExitAlways bool `conf:"EXIT_ALWAYS"`
IgnoreConnectionErrors bool `conf:"IGNORE_CONNECTION_ERRORS"`
StreamURI ct.OptURLAbsolute `conf:"STREAM_URI"`
BaseURI ct.OptURLAbsolute `conf:"BASE_URI"`
ClientSideBaseURI ct.OptURLAbsolute `conf:"CLIENT_SIDE_BASE_URI"`
Port ct.OptIntGreaterThanZero `conf:"PORT"`
InitTimeout ct.OptDuration `conf:"INIT_TIMEOUT"`
HeartbeatInterval ct.OptDuration `conf:"HEARTBEAT_INTERVAL"`
MaxClientConnectionTime ct.OptDuration `conf:"MAX_CLIENT_CONNECTION_TIME"`
DisconnectedStatusTime ct.OptDuration `conf:"DISCONNECTED_STATUS_TIME"`
DisableInternalUsageMetrics bool `conf:"DISABLE_INTERNAL_USAGE_METRICS"`
TLSEnabled bool `conf:"TLS_ENABLED"`
TLSCert string `conf:"TLS_CERT"`
TLSKey string `conf:"TLS_KEY"`
TLSMinVersion OptTLSVersion `conf:"TLS_MIN_VERSION"`
LogLevel OptLogLevel `conf:"LOG_LEVEL"`
BigSegmentsStaleAsDegraded bool `conf:"BIG_SEGMENTS_STALE_AS_DEGRADED"`
BigSegmentsStaleThreshold ct.OptDuration `conf:"BIG_SEGMENTS_STALE_THRESHOLD"`
ExitOnError bool `conf:"EXIT_ON_ERROR"`
ExitAlways bool `conf:"EXIT_ALWAYS"`
IgnoreConnectionErrors bool `conf:"IGNORE_CONNECTION_ERRORS"`
StreamURI ct.OptURLAbsolute `conf:"STREAM_URI"`
BaseURI ct.OptURLAbsolute `conf:"BASE_URI"`
ClientSideBaseURI ct.OptURLAbsolute `conf:"CLIENT_SIDE_BASE_URI"`
Port ct.OptIntGreaterThanZero `conf:"PORT"`
InitTimeout ct.OptDuration `conf:"INIT_TIMEOUT"`
HeartbeatInterval ct.OptDuration `conf:"HEARTBEAT_INTERVAL"`
MaxClientConnectionTime ct.OptDuration `conf:"MAX_CLIENT_CONNECTION_TIME"`
DisconnectedStatusTime ct.OptDuration `conf:"DISCONNECTED_STATUS_TIME"`
TLSEnabled bool `conf:"TLS_ENABLED"`
TLSCert string `conf:"TLS_CERT"`
TLSKey string `conf:"TLS_KEY"`
TLSMinVersion OptTLSVersion `conf:"TLS_MIN_VERSION"`
LogLevel OptLogLevel `conf:"LOG_LEVEL"`
BigSegmentsStaleAsDegraded bool `conf:"BIG_SEGMENTS_STALE_AS_DEGRADED"`
BigSegmentsStaleThreshold ct.OptDuration `conf:"BIG_SEGMENTS_STALE_THRESHOLD"`
}

// AutoConfigConfig contains configuration parameters for the auto-configuration feature.
Expand Down Expand Up @@ -244,6 +248,12 @@ type EnvConfig struct {
SecureMode bool `conf:"LD_SECURE_MODE_"`
LogLevel OptLogLevel `conf:"LD_LOG_LEVEL_"`
TTL ct.OptDuration `conf:"LD_TTL_"`
ProjKey string `conf:"LD_PROJ_KEY_"`
FilterKey FilterKey // injected based on [filters] section
}

type FiltersConfig struct {
Keys ct.OptStringList `conf:"LD_FILTER_KEYS_"`
}

// ProxyConfig represents all the supported proxy options.
Expand Down
82 changes: 76 additions & 6 deletions config/config_field_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"strings"

"github.com/launchdarkly/ld-relay/v8/internal/credential"

"github.com/launchdarkly/go-sdk-common/v3/ldlog"
)

Expand All @@ -31,38 +33,102 @@ 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
// when using this credential, or "" if the header is not used.
GetAuthorizationHeaderValue() string
}
// FilterID represents the unique ID for a filter. It is different from the key, which is scoped to the project
// level.
type FilterID string

// FilterKey represents the key that should be used when making requests to LaunchDarkly in order to obtain
// a filtered environment.
type FilterKey string

// DefaultFilter represents the lack of a filter, meaning a full LaunchDarkly environment.
const DefaultFilter = FilterKey("")

// GetAuthorizationHeaderValue for SDKKey returns the same string, since SDK keys are passed in
// the Authorization header.
func (k SDKKey) GetAuthorizationHeaderValue() string {
return string(k)
}

func (k SDKKey) Defined() bool {
return k != ""
}

func (k SDKKey) String() string {
return string(k)
}

func (k SDKKey) Compare(cr credential.AutoConfig) (credential.SDKCredential, credential.Status) {
if cr.SDKKey == k {
return nil, credential.Unchanged
}
if cr.ExpiringSDKKey == k {
// If the AutoConfig update contains an ExpiringSDKKey that is equal to *this* key, then it means
// this key is now considered deprecated.
return cr.SDKKey, credential.Deprecated
} else {
// Otherwise if the AutoConfig update contains *some other* key, then it means this one must be considered
// expired.
return cr.SDKKey, credential.Expired
}
}

// GetAuthorizationHeaderValue for MobileKey returns the same string, since mobile keys are passed in the
// Authorization header.
func (k MobileKey) GetAuthorizationHeaderValue() string {
return string(k)
}

func (k MobileKey) Defined() bool {
return k != ""
}

func (k MobileKey) String() string {
return string(k)
}

func (k MobileKey) Compare(cr credential.AutoConfig) (credential.SDKCredential, credential.Status) {
if cr.MobileKey == k {
return nil, credential.Unchanged
}
return cr.MobileKey, credential.Expired
}

// GetAuthorizationHeaderValue for EnvironmentID returns an empty string, since environment IDs are not
// passed in a header but as part of the request URL.
func (k EnvironmentID) GetAuthorizationHeaderValue() string {
return ""
}

func (k EnvironmentID) Defined() bool {
return k != ""
}

func (k EnvironmentID) String() string {
return string(k)
}

func (k EnvironmentID) Compare(_ credential.AutoConfig) (credential.SDKCredential, credential.Status) {
// EnvironmentIDs should not change.
return nil, credential.Unchanged
}

// 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)
}

func (k AutoConfigKey) Compare(_ credential.AutoConfig) (credential.SDKCredential, credential.Status) {
// AutoConfigKeys should not change.
return nil, credential.Unchanged
}

func (k AutoConfigKey) String() 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 @@ -87,6 +153,10 @@ func (k *AutoConfigKey) UnmarshalText(data []byte) error {
return nil
}

func (k AutoConfigKey) Defined() bool {
return k != ""
}

// OptLogLevel represents an optional log level parameter. It must match one of the level names "debug",
// "info", "warn", or "error" (case-insensitive).
//
Expand Down
15 changes: 14 additions & 1 deletion config/config_from_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func LoadConfigFromEnvironmentBase(c *Config, loggers ldlog.Loggers) ct.Validati
// The following properties have the same environment variable names in AutoConfigConfig and in
// OfflineModeConfig, because only one of those can be used at a time. We'll blank them out for
// whichever section is not being used.
if c.AutoConfig.Key != "" {
if c.AutoConfig.Key.Defined() {
c.OfflineMode.EnvAllowedOrigin = ct.OptStringList{}
c.OfflineMode.EnvAllowedHeader = ct.OptStringList{}
c.OfflineMode.EnvDatastorePrefix = ""
Expand Down Expand Up @@ -76,6 +76,19 @@ func LoadConfigFromEnvironmentBase(c *Config, loggers ldlog.Loggers) ct.Validati
c.Environment[envName] = &ec
}

for projKey := range reader.FindPrefixedValues("LD_FILTER_KEYS_") {
var fc FiltersConfig
if c.Filters[projKey] != nil {
fc = *c.Filters[projKey]
}
subReader := reader.WithVarNameSuffix(projKey)
subReader.ReadStruct(&fc, false)
if c.Filters == nil {
c.Filters = make(map[string]*FiltersConfig)
}
c.Filters[projKey] = &fc
}

useRedis := false
reader.Read("USE_REDIS", &useRedis)
if useRedis || c.Redis.Host != "" || c.Redis.URL.IsDefined() {
Expand Down
60 changes: 59 additions & 1 deletion config/config_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ var (
errRedisURLWithHostAndPort = errors.New("please specify Redis URL or host/port, but not both")
errRedisBadHostname = errors.New("invalid Redis hostname")
errConsulTokenAndTokenFile = errors.New("Consul token must be specified as either an inline value or a file, but not both") //nolint:stylecheck
errAutoConfWithFilters = errors.New("cannot configure filters if auto-configuration is enabled")
errMissingProjKey = errors.New("when filters are configured, all environments must specify a 'projKey'")
)

func errEnvironmentWithNoSDKKey(envName string) error {
Expand All @@ -38,6 +40,18 @@ func errEnvWithoutDBDisambiguation(envName string, canUseTableName bool) error {
return fmt.Errorf("environment %q does not have a prefix specified for database storage", envName)
}

func errFilterUnknownProject(projKey string) error {
return fmt.Errorf("filters are configured for project '%s', but no environment references that project", projKey)
}

func errFilterEmptyKeys(projKey string) error {
return fmt.Errorf("filter key list for project '%s' cannot be empty", projKey)
}

func errFilterInvalidKey(projKey string, i int) error {
return fmt.Errorf("filter key [%d] for project '%s' is malformed (note: lists are comma-delimited)", i, projKey)
}

func warnEnvWithoutDBDisambiguation(envName string, canUseTableName bool) string {
return errEnvWithoutDBDisambiguation(envName, canUseTableName).Error() +
"; this would be an error if multiple environments were configured"
Expand All @@ -61,6 +75,7 @@ func ValidateConfig(c *Config, loggers ldlog.Loggers) error {
validateConfigTLS(&result, c)
validateConfigEnvironments(&result, c)
validateConfigDatabases(&result, c, loggers)
validateConfigFilters(&result, c)

return result.GetError()
}
Expand Down Expand Up @@ -126,6 +141,49 @@ func validateConfigEnvironments(result *ct.ValidationResult, c *Config) {
}
}

func validateConfigFilters(result *ct.ValidationResult, c *Config) {
if len(c.Filters) == 0 {
return
}
// If Auto Config is enabled, then filters will have no effect and should cause an error.
if c.AutoConfig.Key != "" {
result.AddError(nil, errAutoConfWithFilters)
return
}
for _, proj := range c.Environment {
if proj.ProjKey == "" {
result.AddError(nil, errMissingProjKey)
return
}
}
for projKey, conf := range c.Filters {
// For every project key defined by a [filter] section,
// that project key must be referenced by at least one environment.
foundProj := false
for _, e := range c.Environment {
if e.ProjKey == projKey {
foundProj = true
break
}
}
if !foundProj {
result.AddError(nil, errFilterUnknownProject(projKey))
continue
}

// The list of filter keys cannot be empty
if len(conf.Keys.Values()) == 0 {
result.AddError(nil, errFilterEmptyKeys(projKey))
} else {
// Filter keys cannot be empty strings
for i, k := range conf.Keys.Values() {
if k == "" {
result.AddError(nil, errFilterInvalidKey(projKey, i))
}
}
}
}
}
func validateConfigDatabases(result *ct.ValidationResult, c *Config, loggers ldlog.Loggers) {
normalizeRedisConfig(result, c)

Expand Down Expand Up @@ -172,7 +230,7 @@ func validateConfigDatabases(result *ct.ValidationResult, c *Config, loggers ldl
}
}

case c.AutoConfig.Key != "":
case c.AutoConfig.Key.Defined():
// Same as previous case, except that in auto-config mode we must assume that there are multiple environments.
if !strings.Contains(c.AutoConfig.EnvDatastorePrefix, AutoConfigEnvironmentIDPlaceholder) &&
!(c.DynamoDB.Enabled && strings.Contains(c.AutoConfig.EnvDatastoreTableName, AutoConfigEnvironmentIDPlaceholder)) {
Expand Down
Loading

0 comments on commit 7d67cb6

Please sign in to comment.