-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #171 from launchdarkly/eb/ch87406/undelete-core
(v6 - #1) revert deletion of core packages, don't use ld-relay-core
- Loading branch information
Showing
134 changed files
with
11,588 additions
and
287 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package application | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"io" | ||
"os" | ||
"strings" | ||
) | ||
|
||
// DefaultConfigPath is the default configuration file path. | ||
const DefaultConfigPath = "/etc/ld-relay.conf" | ||
|
||
// Options represents all options that can be set from the command line. | ||
type Options struct { | ||
ConfigFile string | ||
AllowMissingFile bool | ||
UseEnvironment bool | ||
} | ||
|
||
func errConfigFileNotFound(filename string) error { | ||
return fmt.Errorf("configuration file %q does not exist", filename) | ||
} | ||
|
||
// DescribeConfigSource returns a human-readable phrase describing whether the configuration comes from a | ||
// file, from variables, or both. | ||
func (o Options) DescribeConfigSource() string { | ||
if o.ConfigFile == "" && o.UseEnvironment { | ||
return "configuration from environment variables" | ||
} | ||
desc := "" | ||
if o.ConfigFile != "" { | ||
desc = fmt.Sprintf("configuration file %s", o.ConfigFile) | ||
} | ||
if o.UseEnvironment { | ||
desc += " plus environment variables" | ||
} | ||
return desc | ||
} | ||
|
||
// ReadOptions reads and validates the command-line options. | ||
// | ||
// The configuration parameter behavior is as follows: | ||
// 1. If you specify --config $FILEPATH, it loads that file. Failure to find it or parse it is a fatal error, | ||
// unless you also specify --allow-missing-file. | ||
// 2. If you specify --from-env, it creates a configuration from environment variables as described in README. | ||
// 3. If you specify both, the file is loaded first, then it applies changes from variables if any. | ||
// 4. Omitting all options is equivalent to explicitly specifying --config /etc/ld-relay.conf. | ||
func ReadOptions(osArgs []string, errorOutput io.Writer) (Options, error) { | ||
var o Options | ||
|
||
fs := flag.NewFlagSet("", flag.ContinueOnError) | ||
fs.SetOutput(errorOutput) | ||
fs.StringVar(&o.ConfigFile, "config", "", "configuration file location") | ||
fs.BoolVar(&o.AllowMissingFile, "allow-missing-file", false, "suppress error if config file is not found") | ||
fs.BoolVar(&o.UseEnvironment, "from-env", false, "read configuration from environment variables") | ||
err := fs.Parse(osArgs[1:]) | ||
if err != nil { | ||
return o, err | ||
} | ||
|
||
if o.ConfigFile == "" && !o.UseEnvironment { | ||
o.ConfigFile = DefaultConfigPath | ||
} | ||
|
||
if o.ConfigFile != "" { | ||
_, err := os.Stat(o.ConfigFile) | ||
fileExists := err == nil || !os.IsNotExist(err) | ||
if !fileExists { | ||
if !o.AllowMissingFile { | ||
return o, errConfigFileNotFound(o.ConfigFile) | ||
} | ||
o.ConfigFile = "" | ||
} | ||
} | ||
|
||
return o, nil | ||
} | ||
|
||
// DescribeRelayVersion returns the same version string unless it is a prerelease build, in | ||
// which case it is reformatted to change "+xxx" into "(build xxx)". | ||
func DescribeRelayVersion(version string) string { | ||
split := strings.Split(version, "+") | ||
if len(split) == 2 { | ||
return fmt.Sprintf("%s (build %s)", split[0], split[1]) | ||
} | ||
return version | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package application | ||
|
||
import ( | ||
"io/ioutil" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
helpers "github.com/launchdarkly/go-test-helpers/v2" | ||
) | ||
|
||
func TestReadOptions(t *testing.T) { | ||
appName := "ld-relay" | ||
|
||
t.Run("default config file path", func(t *testing.T) { | ||
_, err := ReadOptions([]string{appName}, ioutil.Discard) | ||
require.Error(t, err) | ||
assert.Equal(t, errConfigFileNotFound(DefaultConfigPath), err) | ||
}) | ||
|
||
t.Run("allow missing file with default path", func(t *testing.T) { | ||
opts, err := ReadOptions([]string{appName, "--allow-missing-file"}, ioutil.Discard) | ||
require.NoError(t, err) | ||
assert.Equal(t, "", opts.ConfigFile) | ||
assert.False(t, opts.UseEnvironment) | ||
}) | ||
|
||
t.Run("custom config file", func(t *testing.T) { | ||
helpers.WithTempFile(func(filename string) { | ||
opts, err := ReadOptions([]string{appName, "--config", filename}, ioutil.Discard) | ||
require.NoError(t, err) | ||
assert.Equal(t, filename, opts.ConfigFile) | ||
assert.False(t, opts.UseEnvironment) | ||
assert.Equal(t, "configuration file "+filename, opts.DescribeConfigSource()) | ||
}) | ||
}) | ||
|
||
t.Run("environment only", func(t *testing.T) { | ||
opts, err := ReadOptions([]string{appName, "--from-env"}, ioutil.Discard) | ||
require.NoError(t, err) | ||
assert.Equal(t, "", opts.ConfigFile) | ||
assert.True(t, opts.UseEnvironment) | ||
assert.Equal(t, "configuration from environment variables", opts.DescribeConfigSource()) | ||
}) | ||
|
||
t.Run("environment plus config file", func(t *testing.T) { | ||
helpers.WithTempFile(func(filename string) { | ||
opts, err := ReadOptions([]string{appName, "--config", filename, "--from-env"}, ioutil.Discard) | ||
require.NoError(t, err) | ||
assert.Equal(t, filename, opts.ConfigFile) | ||
assert.True(t, opts.UseEnvironment) | ||
assert.Equal(t, "configuration file "+filename+" plus environment variables", opts.DescribeConfigSource()) | ||
}) | ||
}) | ||
|
||
t.Run("invalid options", func(t *testing.T) { | ||
_, err := ReadOptions([]string{appName, "--unknown"}, ioutil.Discard) | ||
assert.Error(t, err) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// Package application providers helpers used by the command-line entry points of all versions of Relay. | ||
package application |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package application | ||
|
||
import ( | ||
"crypto/tls" | ||
"fmt" | ||
"net/http" | ||
|
||
config "github.com/launchdarkly/ld-relay-config" | ||
"gopkg.in/launchdarkly/go-sdk-common.v2/ldlog" | ||
) | ||
|
||
// StartHTTPServer starts the server, with or without TLS. It returns immediately, starting the server | ||
// on a separate goroutine; if the server fails to start up, it sends an error to the error channel. | ||
func StartHTTPServer( | ||
port int, | ||
handler http.Handler, | ||
tlsEnabled bool, | ||
tlsCertFile, tlsKeyFile string, | ||
tlsMinVersion uint16, | ||
loggers ldlog.Loggers, | ||
) <-chan error { | ||
srv := &http.Server{ | ||
Addr: fmt.Sprintf(":%d", port), | ||
Handler: handler, | ||
} | ||
|
||
if tlsEnabled && tlsMinVersion != 0 { | ||
srv.TLSConfig = &tls.Config{ | ||
MinVersion: tlsMinVersion, | ||
} | ||
} | ||
|
||
errCh := make(chan error) | ||
|
||
go func() { | ||
var err error | ||
loggers.Infof("Starting server listening on port %d\n", port) | ||
if tlsEnabled { | ||
message := "TLS enabled for server" | ||
if tlsMinVersion != 0 { | ||
message += fmt.Sprintf(" (minimum TLS version: %s)", config.NewOptTLSVersion(tlsMinVersion).String()) | ||
} | ||
loggers.Info(message) | ||
err = srv.ListenAndServeTLS(tlsCertFile, tlsKeyFile) | ||
} else { | ||
err = srv.ListenAndServe() | ||
} | ||
if err != nil { | ||
errCh <- err | ||
} | ||
}() | ||
|
||
return errCh | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package core | ||
|
||
import ( | ||
"bytes" | ||
"encoding/base64" | ||
"net/http" | ||
"net/http/httptest" | ||
"strconv" | ||
|
||
"github.com/launchdarkly/ld-relay/v6/core/internal/browser" | ||
"github.com/launchdarkly/ld-relay/v6/core/internal/events" | ||
"github.com/launchdarkly/ld-relay/v6/core/internal/util" | ||
"github.com/launchdarkly/ld-relay/v6/core/middleware" | ||
) | ||
|
||
func getEventsImage(w http.ResponseWriter, req *http.Request) { | ||
clientCtx := middleware.GetEnvContextInfo(req.Context()) | ||
|
||
if clientCtx.Env.GetEventDispatcher() == nil { | ||
w.WriteHeader(http.StatusServiceUnavailable) | ||
_, _ = w.Write(util.ErrorJSONMsg("Event proxy is not enabled for this environment")) | ||
return | ||
} | ||
handler := clientCtx.Env.GetEventDispatcher().GetHandler(events.JavaScriptSDKEventsEndpoint) | ||
if handler == nil { | ||
w.WriteHeader(http.StatusServiceUnavailable) | ||
_, _ = w.Write(util.ErrorJSONMsg("Event proxy for browser clients is not enabled for this environment")) | ||
return | ||
} | ||
|
||
d := req.URL.Query().Get("d") | ||
if d != "" { | ||
go func() { | ||
nullW := httptest.NewRecorder() | ||
eventData, _ := base64.StdEncoding.DecodeString(d) | ||
eventsReq, _ := http.NewRequest("POST", "", bytes.NewBuffer(eventData)) | ||
eventsReq.Header.Add("Content-Type", "application/json") | ||
eventsReq.Header.Add("X-LaunchDarkly-User-Agent", eventsReq.Header.Get("X-LaunchDarkly-User-Agent")) | ||
eventsReq.Header.Add(events.EventSchemaHeader, strconv.Itoa(events.SummaryEventsSchemaVersion)) | ||
handler(nullW, eventsReq) | ||
}() | ||
} | ||
|
||
w.Header().Set("Content-Type", "image/gif") | ||
_, _ = w.Write(browser.Transparent1PixelImageData) | ||
} | ||
|
||
func getGoals(w http.ResponseWriter, req *http.Request) { | ||
clientCtx := middleware.GetEnvContextInfo(req.Context()) | ||
clientCtx.Env.GetJSClientContext().Proxy.ServeHTTP(w, req) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Package httpconfig provides helpers for special types of HTTP client configuration supported by Relay. | ||
package httpconfig | ||
|
||
import ( | ||
"errors" | ||
"net/http" | ||
|
||
config "github.com/launchdarkly/ld-relay-config" | ||
"gopkg.in/launchdarkly/go-sdk-common.v2/ldlog" | ||
"gopkg.in/launchdarkly/go-server-sdk.v5/interfaces" | ||
"gopkg.in/launchdarkly/go-server-sdk.v5/ldcomponents" | ||
"gopkg.in/launchdarkly/go-server-sdk.v5/ldhttp" | ||
"gopkg.in/launchdarkly/go-server-sdk.v5/ldntlm" | ||
) | ||
|
||
var ( | ||
errNTLMProxyAuthWithoutCredentials = errors.New("NTLM proxy authentication requires username and password") | ||
errProxyAuthWithoutProxyURL = errors.New("cannot specify proxy authentication without a proxy URL") | ||
) | ||
|
||
// HTTPConfig encapsulates ProxyConfig plus any other HTTP options we may support in the future (currently none). | ||
type HTTPConfig struct { | ||
config.ProxyConfig | ||
SDKHTTPConfigFactory interfaces.HTTPConfigurationFactory | ||
SDKHTTPConfig interfaces.HTTPConfiguration | ||
} | ||
|
||
// NewHTTPConfig validates all of the HTTP-related options and returns an HTTPConfig if successful. | ||
func NewHTTPConfig(proxyConfig config.ProxyConfig, authKey config.SDKCredential, userAgent string, loggers ldlog.Loggers) (HTTPConfig, error) { | ||
configBuilder := ldcomponents.HTTPConfiguration() | ||
configBuilder.UserAgent(userAgent) | ||
|
||
ret := HTTPConfig{ProxyConfig: proxyConfig} | ||
|
||
authKeyStr := "" | ||
if authKey != nil { | ||
authKeyStr = authKey.GetAuthorizationHeaderValue() | ||
} | ||
|
||
if !proxyConfig.URL.IsDefined() && proxyConfig.NTLMAuth { | ||
return ret, errProxyAuthWithoutProxyURL | ||
} | ||
if proxyConfig.URL.IsDefined() { | ||
loggers.Infof("Using proxy server at %s", proxyConfig.URL) | ||
} | ||
|
||
caCertFiles := proxyConfig.CACertFiles.Values() | ||
|
||
if proxyConfig.NTLMAuth { | ||
if proxyConfig.User == "" || proxyConfig.Password == "" { | ||
return ret, errNTLMProxyAuthWithoutCredentials | ||
} | ||
transportOpts := []ldhttp.TransportOption{ | ||
ldhttp.ConnectTimeoutOption(ldcomponents.DefaultConnectTimeout), | ||
} | ||
for _, filePath := range caCertFiles { | ||
if filePath != "" { | ||
transportOpts = append(transportOpts, ldhttp.CACertFileOption(filePath)) | ||
} | ||
} | ||
factory, err := ldntlm.NewNTLMProxyHTTPClientFactory(proxyConfig.URL.String(), | ||
proxyConfig.User, proxyConfig.Password, proxyConfig.Domain, transportOpts...) | ||
if err != nil { | ||
return ret, err | ||
} | ||
configBuilder.HTTPClientFactory(factory) | ||
loggers.Info("NTLM proxy authentication enabled") | ||
} else { | ||
if proxyConfig.URL.IsDefined() { | ||
configBuilder.ProxyURL(proxyConfig.URL.String()) | ||
} | ||
for _, filePath := range caCertFiles { | ||
if filePath != "" { | ||
configBuilder.CACertFile(filePath) | ||
} | ||
} | ||
} | ||
|
||
var err error | ||
ret.SDKHTTPConfigFactory = configBuilder | ||
ret.SDKHTTPConfig, err = configBuilder.CreateHTTPConfiguration(interfaces.BasicConfiguration{SDKKey: authKeyStr}) | ||
return ret, err | ||
} | ||
|
||
// Client creates a new HTTP client instance that isn't for SDK use. | ||
func (c HTTPConfig) Client() *http.Client { | ||
return c.SDKHTTPConfig.CreateHTTPClient() | ||
} |
Oops, something went wrong.