<|start_filename|>instrument/global.go<|end_filename|>
package instrument

import (
	"sync"

	"github.com/sirupsen/logrus"
	"gopkg.in/segmentio/analytics-go.v3"
)

var globalLock sync.Mutex
var globalClient Client = MockClient{}

func SetGlobalClient(client Client) {
	if client == nil {
		return
	}
	globalLock.Lock()
	globalClient = client
	globalLock.Unlock()
}

func GetGlobalClient() Client {
	globalLock.Lock()
	defer globalLock.Unlock()
	return globalClient
}

// Init will initialize global client with a segment client
func Init(conf Config, log logrus.FieldLogger) error {
	segmentClient, err := NewClient(&conf, log)
	if err != nil {
		return err
	}
	SetGlobalClient(segmentClient)
	return nil
}

// Identify sends an identify type message to a queue to be sent to Segment.
func Identify(userID string, traits analytics.Traits) error {
	return GetGlobalClient().Identify(userID, traits)
}

// Track sends a track type message to a queue to be sent to Segment.
func Track(userID string, event string, properties analytics.Properties) error {
	return GetGlobalClient().Track(userID, event, properties)
}

// Page sends a page type message to a queue to be sent to Segment.
func Page(userID string, name string, properties analytics.Properties) error {
	return GetGlobalClient().Page(userID, name, properties)
}

// Group sends a group type message to a queue to be sent to Segment.
func Group(userID string, groupID string, traits analytics.Traits) error {
	return GetGlobalClient().Group(userID, groupID, traits)
}

// Alias sends an alias type message to a queue to be sent to Segment.
func Alias(previousID string, userID string) error {
	return GetGlobalClient().Alias(previousID, userID)
}


<|start_filename|>nconf/args.go<|end_filename|>
package nconf

import (
	"fmt"
	"strings"

	"github.com/netlify/netlify-commons/featureflag"
	"github.com/netlify/netlify-commons/instrument"
	"github.com/netlify/netlify-commons/metriks"
	"github.com/netlify/netlify-commons/tracing"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

type RootArgs struct {
	Prefix     string
	ConfigFile string
}

func (args *RootArgs) Setup(config interface{}, serviceName, version string) (logrus.FieldLogger, error) {
	rootConfig, err := args.loadDefaultConfig()
	if err != nil {
		return nil, err
	}

	log, err := ConfigureLogging(rootConfig.Log)
	if err != nil {
		return nil, errors.Wrap(err, "Failed to create the logger")
	}
	if version == "" {
		version = "unknown"
	}
	log = log.WithField("version", version)

	if err := SetupBugSnag(rootConfig.BugSnag, version); err != nil {
		return nil, errors.Wrap(err, "Failed to configure bugsnag")
	}

	if err := metriks.Init(serviceName, rootConfig.Metrics); err != nil {
		return nil, errors.Wrap(err, "Failed to configure metrics")
	}

	// Handles the 'enabled' flag itself
	tracing.Configure(&rootConfig.Tracing, log, serviceName)

	if err := featureflag.Init(rootConfig.FeatureFlag, log); err != nil {
		return nil, errors.Wrap(err, "Failed to configure featureflags")
	}

	if err := instrument.Init(rootConfig.Instrument, log); err != nil {
		return nil, errors.Wrap(err, "Failed to configure instrument")
	}

	if err := sendDatadogEvents(rootConfig.Metrics, serviceName, version); err != nil {
		log.WithError(err).Error("Failed to send the startup events to datadog")
	}

	if config != nil {
		// second load the config for this project
		if err := args.load(config); err != nil {
			return log, errors.Wrap(err, "Failed to load the config object")
		}
		log.Debug("Loaded configuration")
	}
	return log, nil
}

func (args *RootArgs) load(cfg interface{}) error {
	loader := func(cfg interface{}) error {
		return LoadFromEnv(args.Prefix, args.ConfigFile, cfg)
	}
	if !strings.HasSuffix(args.ConfigFile, ".env") {
		loader = func(cfg interface{}) error {
			return LoadConfigFromFile(args.ConfigFile, cfg)
		}
	}
	return loader(cfg)
}

func (args *RootArgs) MustSetup(config interface{}, serviceName, version string) logrus.FieldLogger {
	logger, err := args.Setup(config, serviceName, version)
	if err != nil {
		if logger != nil {
			logger.WithError(err).Fatal("Failed to setup configuration")
		} else {
			panic(fmt.Sprintf("Failed to setup configuration: %s", err.Error()))
		}
	}

	return logger
}

func (args *RootArgs) loadDefaultConfig() (*RootConfig, error) {
	c := DefaultConfig()

	if err := args.load(&c); err != nil {
		return nil, errors.Wrap(err, "Failed to load the default configuration")
	}

	return &c, nil
}

func (args *RootArgs) AddFlags(cmd *cobra.Command) *cobra.Command {
	cmd.Flags().AddFlag(args.ConfigFlag())
	cmd.Flags().AddFlag(args.PrefixFlag())
	return cmd
}

func (args *RootArgs) ConfigFlag() *pflag.Flag {
	return &pflag.Flag{
		Name:      "config",
		Shorthand: "c",
		Usage:     "A file to load configuration from, supported formats are env, json, and yaml",
		Value:     newStringValue("", &args.ConfigFile),
	}
}

func (args *RootArgs) PrefixFlag() *pflag.Flag {
	return &pflag.Flag{
		Name:      "prefix",
		Shorthand: "p",
		Usage:     "A prefix to search for when looking for env vars",
		Value:     newStringValue("", &args.Prefix),
	}
}

type stringValue string

func newStringValue(val string, p *string) *stringValue {
	*p = val
	return (*stringValue)(p)
}

func (s *stringValue) Set(val string) error {
	*s = stringValue(val)
	return nil
}
func (s *stringValue) Type() string   { return "string" }
func (s *stringValue) String() string { return string(*s) }


<|start_filename|>nconf/timeout_test.go<|end_filename|>
package nconf

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/yaml.v3"
)

func TestParseTimeoutValues(t *testing.T) {
	raw := map[string]string{
		"dial":            "10s",
		"keep_alive":      "11s",
		"tls_handshake":   "12s",
		"response_header": "13s",
		"total":           "14s",
	}

	// write it to json & yaml
	// then load it through the RootArgs.load
	scenes := []struct {
		name string
		enc  func(interface{}) ([]byte, error)
		dec  func([]byte, interface{}) error
	}{
		{"json", json.Marshal, json.Unmarshal},
		{"yaml", yaml.Marshal, yaml.Unmarshal},
	}
	for _, s := range scenes {
		t.Run(s.name, func(t *testing.T) {
			bs, err := s.enc(&raw)
			require.NoError(t, err)

			var cfg HTTPClientTimeoutConfig

			require.NoError(t, s.dec(bs, &cfg))

			assert.Equal(t, "10s", cfg.Dial.String())
			assert.Equal(t, "11s", cfg.KeepAlive.String())
			assert.Equal(t, "12s", cfg.TLSHandshake.String())
			assert.Equal(t, "13s", cfg.ResponseHeader.String())
			assert.Equal(t, "14s", cfg.Total.String())
			assert.Equal(t, 10*time.Second, cfg.Dial.Duration)
			assert.Equal(t, 11*time.Second, cfg.KeepAlive.Duration)
			assert.Equal(t, 12*time.Second, cfg.TLSHandshake.Duration)
			assert.Equal(t, 13*time.Second, cfg.ResponseHeader.Duration)
			assert.Equal(t, 14*time.Second, cfg.Total.Duration)
		})
	}
}


<|start_filename|>util/duration_test.go<|end_filename|>
package util

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/yaml.v3"
)

func TestDuration_Unmarshal(t *testing.T) {
	testCases := []struct {
		name      string
		url       string
		expected  time.Duration
		errCheck  require.ErrorAssertionFunc
		unmarshal func([]byte, interface{}) error
	}{
		{"empty-json", `{"d": ""}`, 0, require.Error, json.Unmarshal},
		{"invalid-json", `{"d": "no duration here"}`, 0, require.Error, json.Unmarshal},
		{"valid-json-string", `{"d": "1s"}`, time.Second, require.NoError, json.Unmarshal},
		{"valid-json-int", `{"d": 1000000000}`, time.Second, require.NoError, json.Unmarshal},
		{"valid-json-float", `{"d": 1000000000.0}`, time.Second, require.NoError, json.Unmarshal},

		{"empty-yaml", `u: ""}`, 0, require.Error, yaml.Unmarshal},
		{"invalid-yaml", `u: "no duration here"}`, 0, require.Error, yaml.Unmarshal},
		{"valid-yaml-string", `{"d": "1s"}`, time.Second, require.NoError, yaml.Unmarshal},
		{"valid-yaml-int", `{"d": 1000000000}`, time.Second, require.NoError, yaml.Unmarshal},
		{"valid-yaml-float", `{"d": 1000000000.0}`, time.Second, require.NoError, yaml.Unmarshal},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var s struct {
				D Duration `json:"d"`
			}
			tc.errCheck(t, tc.unmarshal([]byte(tc.url), &s))
			assert.Equal(t, tc.expected, s.D.Duration)
		})
	}
}

func TestDuration_Marshal(t *testing.T) {
	testCases := []struct {
		name     string
		marshal  func(interface{}) ([]byte, error)
		expected string
	}{
		{"json", json.Marshal, `{"d":"1s"}`},
		{"yaml", yaml.Marshal, "d: 1s\n"},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			u := struct {
				D Duration `json:"d" yaml:"d"`
			}{D: Duration{time.Second}}

			serialized, err := tc.marshal(u)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, string(serialized))
		})
	}
}


<|start_filename|>tracing/opentracing.go<|end_filename|>
package tracing

import (
	"fmt"

	"github.com/opentracing/opentracing-go"
	otlog "github.com/opentracing/opentracing-go/log"
	"github.com/pkg/errors"
)

type stackTracer interface {
	StackTrace() errors.StackTrace
}

func LogErrorToSpan(span opentracing.Span, err error) {
	if err == nil || span == nil {
		return
	}

	span.LogFields(otlog.String("event", "error"), otlog.Error(err))
	if sterr, ok := err.(stackTracer); ok {
		span.LogFields(otlog.String("stack", fmt.Sprintf("%+v", sterr.StackTrace())))
	}
}


<|start_filename|>instrument/instrument.go<|end_filename|>
package instrument

import (
	"io/ioutil"

	"github.com/sirupsen/logrus"
	"gopkg.in/segmentio/analytics-go.v3"
)

type Client interface {
	Identify(userID string, traits analytics.Traits) error
	Track(userID string, event string, properties analytics.Properties) error
	Page(userID string, name string, properties analytics.Properties) error
	Group(userID string, groupID string, traits analytics.Traits) error
	Alias(previousID string, userID string) error
}

type segmentClient struct {
	analytics.Client
	log logrus.FieldLogger
}

var _ Client = &segmentClient{}

func NewClient(cfg *Config, logger logrus.FieldLogger) (Client, error) {
	config := analytics.Config{}

	if !cfg.Enabled {
		// use mockClient instead
		return &MockClient{logger}, nil
	}

	configureLogger(&config, logger)

	inner, err := analytics.NewWithConfig(cfg.Key, config)
	if err != nil {
		logger.WithError(err).Error("Unable to construct Segment client")
	}
	return &segmentClient{inner, logger}, err
}

func (c segmentClient) Identify(userID string, traits analytics.Traits) error {
	return c.Client.Enqueue(analytics.Identify{
		UserId: userID,
		Traits: traits,
	})
}

func (c segmentClient) Track(userID string, event string, properties analytics.Properties) error {
	return c.Client.Enqueue(analytics.Track{
		UserId:     userID,
		Event:      event,
		Properties: properties,
	})
}

func (c segmentClient) Page(userID string, name string, properties analytics.Properties) error {
	return c.Client.Enqueue(analytics.Page{
		UserId:     userID,
		Name:       name,
		Properties: properties,
	})
}

func (c segmentClient) Group(userID string, groupID string, traits analytics.Traits) error {
	return c.Client.Enqueue(analytics.Group{
		UserId:  userID,
		GroupId: groupID,
		Traits:  traits,
	})
}

func (c segmentClient) Alias(previousID string, userID string) error {
	return c.Client.Enqueue(analytics.Alias{
		PreviousId: previousID,
		UserId:     userID,
	})
}

func configureLogger(conf *analytics.Config, log logrus.FieldLogger) {
	if log == nil {
		l := logrus.New()
		l.SetOutput(ioutil.Discard)
		log = l
	}
	log = log.WithField("component", "segment")
	conf.Logger = &wrapLog{log.Printf, log.Errorf}
}

type wrapLog struct {
	printf func(format string, args ...interface{})
	errorf func(format string, args ...interface{})
}

// Logf implements analytics.Logger interface
func (l *wrapLog) Logf(format string, args ...interface{}) {
	l.printf(format, args...)
}

// Errorf implements analytics.Logger interface
func (l *wrapLog) Errorf(format string, args ...interface{}) {
	l.errorf(format, args...)
}


<|start_filename|>metriks/config.go<|end_filename|>
package metriks

type Config struct {
	Enabled bool
	Host    string `default:"localhost"`
	Port    int    `default:"8125"`

	Tags map[string]string
}


<|start_filename|>router/middleware_test.go<|end_filename|>
package router

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/netlify/netlify-commons/tracing"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/mocktracer"
	"github.com/sirupsen/logrus"
	"github.com/sirupsen/logrus/hooks/test"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
)

func TestCheckAuth(t *testing.T) {
	validKey := "testkey"
	invalidKey := "nopekey"
	emptyKey := ""

	makeRequest := func(req *http.Request) *httptest.ResponseRecorder {
		r := New(logrus.WithField("test", "CheckAuth"))
		r.Use(CheckAuth(validKey))
		r.Get("/", func(w http.ResponseWriter, r *http.Request) error {
			return nil
		})
		rec := httptest.NewRecorder()
		r.ServeHTTP(rec, req)
		return rec
	}

	t.Run("valid key", func(t *testing.T) {
		req, err := http.NewRequest("GET", "/", nil)
		require.NoError(t, err)
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", validKey))
		rsp := makeRequest(req)
		assert.Equal(t, http.StatusOK, rsp.Code)
	})
	t.Run("lower case bearer", func(t *testing.T) {
		req, err := http.NewRequest("GET", "/", nil)
		require.NoError(t, err)
		req.Header.Set("Authorization", fmt.Sprintf("bearer %s", validKey))
		rsp := makeRequest(req)
		assert.Equal(t, http.StatusOK, rsp.Code)
	})

	t.Run("invalid key", func(t *testing.T) {
		req, err := http.NewRequest("GET", "/", nil)
		require.NoError(t, err)
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", invalidKey))
		rsp := makeRequest(req)
		assert.Equal(t, http.StatusUnauthorized, rsp.Code)
	})
	t.Run("no header", func(t *testing.T) {
		req, err := http.NewRequest("GET", "/", nil)
		require.NoError(t, err)
		rsp := makeRequest(req)
		assert.Equal(t, http.StatusUnauthorized, rsp.Code)
	})
	t.Run("empty key", func(t *testing.T) {
		req, err := http.NewRequest("GET", "/", nil)
		require.NoError(t, err)
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", emptyKey))
		rsp := makeRequest(req)
		assert.Equal(t, http.StatusUnauthorized, rsp.Code)
	})
	t.Run("invalid Authorization value", func(t *testing.T) {
		req, err := http.NewRequest("GET", "/", nil)
		require.NoError(t, err)
		req.Header.Set("Authorization", fmt.Sprintf("what even is this %s", invalidKey))
		rsp := makeRequest(req)
		assert.Equal(t, http.StatusUnauthorized, rsp.Code)
	})

}

func TestRecoveryLogging(t *testing.T) {
	logger, hook := test.NewNullLogger()

	mw := Recoverer(logger)

	rec := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "http://doesntmatter.com", nil)
	req.Header.Set(tracing.HeaderRequestUUID, "123456")

	// this should be captured by the recorder
	handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		panic(errors.New("because I should"))
	}))

	handler.ServeHTTP(rec, req)
	require.NotEmpty(t, hook.AllEntries)
	var lineID int
	for _, e := range hook.AllEntries() {
		assert.Equal(t, "123456", e.Data["request_id"], "missing the request_id: %v", e.Data)
		assert.Equal(t, lineID, e.Data["trace_line"], "trace_line isn't in order: %v", e.Data)
		lineID++
	}
}

func TestRecoveryTracing(t *testing.T) {
	mw := Recoverer(logrus.New())
	handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		panic(errors.New("because I should"))
	}))

	mtracer := mocktracer.New()
	r := httptest.NewRequest(http.MethodGet, "/", nil)
	r.Header.Set(tracing.HeaderRequestUUID, "123456")
	ctx := opentracing.ContextWithSpan(r.Context(), mtracer.StartSpan(t.Name()))
	r = r.WithContext(ctx)
	w := httptest.NewRecorder()
	handler.ServeHTTP(w, r)

	finished := mtracer.FinishedSpans()
	assert.Len(t, finished, 1)

	tags := finished[0].Tags()
	assert.Len(t, tags, 4)
	assert.Equal(t, "123456", tags["error_id"])
	assert.Equal(t, "because I should", tags[ext.ErrorMsg])
	assert.Equal(t, "panic", tags[ext.ErrorType])
	assert.Equal(t, 500, tags[ext.HTTPCode])
}

func TestRecoveryInternalTracer(t *testing.T) {
	logger, hook := test.NewNullLogger()
	w, r, _ := tracing.NewTracer(
		httptest.NewRecorder(),
		httptest.NewRequest(http.MethodGet, "/", nil),
		logger,
		t.Name(),
		"some_resource",
	)
	mw := Recoverer(logrus.New())
	handler := mw(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		panic(errors.New("because I should"))
	}))
	handler.ServeHTTP(w, r)

	var found bool
	for _, e := range hook.Entries {
		found = e.Message == "Completed Request"
		if found {
			assert.Equal(t, 500, e.Data["status_code"])
			break
		}
	}
	assert.True(t, found)
}


<|start_filename|>instrument/instrument_test.go<|end_filename|>
package instrument

import (
	"reflect"
	"testing"

	"github.com/netlify/netlify-commons/testutil"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/segmentio/analytics-go.v3"
)

func TestLogOnlyClient(t *testing.T) {
	cfg := Config{
		Key:     "ABCD",
		Enabled: false,
	}
	client, err := NewClient(&cfg, nil)
	require.NoError(t, err)

	require.Equal(t, reflect.TypeOf(&MockClient{}).Kind(), reflect.TypeOf(client).Kind())
}

func TestMockClient(t *testing.T) {
	log := testutil.TL(t)
	mock := MockClient{log}

	require.NoError(t, mock.Identify("myuser", analytics.NewTraits().SetName("My User")))
}

func TestLogging(t *testing.T) {
	cfg := Config{
		Key: "ABCD",
	}

	log, hook := testutil.TestLogger(t)

	client, err := NewClient(&cfg, log.WithField("component", "segment"))
	require.NoError(t, err)
	require.NoError(t, client.Identify("myuser", analytics.NewTraits().SetName("My User")))
	assert.NotEmpty(t, hook.LastEntry())
}


<|start_filename|>trafficmesh/signature_test.go<|end_filename|>
package trafficmesh

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/dgrijalva/jwt-go"
	"github.com/stretchr/testify/require"
)

func TestSignatureDecoder(t *testing.T) {
	const url = "https://example.org/index.html"

	var makeReq = func() *http.Request {
		req := httptest.NewRequest(http.MethodGet, "https://192.0.2.1/index.html", nil)
		req.Host = "example.org"
		return req
	}

	var addToken = func(req *http.Request, secret, url string) {
		token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
			"sid":      "1",
			"did":      "2",
			"url":      url,
			"remapped": true,
		})
		sig, err := token.SignedString([]byte(secret))
		require.NoError(t, err)
		req.Header.Set(signatureHeader, sig)
	}

	dec := NewSignatureDecoder("secret")

	t.Run("no token", func(t *testing.T) {
		req := makeReq()
		payload, err := dec.DecodeSignature(req)
		require.NoError(t, err)
		require.Nil(t, payload)
	})

	t.Run("valid token", func(t *testing.T) {
		req := makeReq()
		addToken(req, "secret", url)

		payload, err := dec.DecodeSignature(req)
		require.NoError(t, err)

		require.Equal(t, payload.SiteID, "1")
		require.Equal(t, payload.DeployID, "2")
	})

	t.Run("wrong secret", func(t *testing.T) {
		req := makeReq()
		addToken(req, "wrong-secret", url)

		_, err := dec.DecodeSignature(req)
		require.Error(t, err)
	})

	t.Run("wrong path", func(t *testing.T) {
		req := makeReq()
		addToken(req, "secret", "http://example.org/stuff")
		_, err := dec.DecodeSignature(req)
		require.Error(t, err)
	})

	t.Run("wrong host", func(t *testing.T) {
		req := makeReq()
		addToken(req, "secret", "http://example.net/index.html")
		_, err := dec.DecodeSignature(req)
		require.Error(t, err)
	})

	t.Run("remapped flag", func(t *testing.T) {
		req := makeReq()
		addToken(req, "secret", url)
		payload, err := dec.DecodeSignature(req)
		require.NoError(t, err)
		require.True(t, payload.Remapped)
	})
}


<|start_filename|>mongoclient/options.go<|end_filename|>
package mongoclient

import (
	"strings"
	"time"

	"github.com/netlify/netlify-commons/nconf"
	"github.com/sirupsen/logrus"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
)

type Option func(opt *options.ClientOptions) error

func TLSOption(log logrus.FieldLogger, config nconf.TLSConfig) Option {
	return func(opts *options.ClientOptions) error {
		if !config.Enabled {
			log.Debug("Skipping TLS config")
			return nil
		}

		log.WithFields(logrus.Fields{
			"cert_file": config.CertFile,
			"key_file":  config.KeyFile,
			"ca_files":  strings.Join(config.CAFiles, ","),
		}).Debug("Using TLS config")

		tlsConfig, err := config.TLSConfig()
		if err != nil {
			return err
		}

		opts.SetTLSConfig(tlsConfig)
		return nil
	}
}

func AuthOption(config Auth) Option {
	return func(opts *options.ClientOptions) error {
		creds := options.Credential{
			Username:   config.Username,
			Password:   config.Password,
			AuthSource: config.Source,
		}

		opts.SetAuth(creds)
		return nil
	}
}

func AppName(name string) Option {
	return func(opts *options.ClientOptions) error {
		if name != "" {
			opts.SetAppName(name)
		}
		return nil
	}
}

func SecondaryPreferred() Option {
	return func(opts *options.ClientOptions) error {
		opts.SetReadPreference(readpref.SecondaryPreferred())
		return nil
	}
}

func ConnectionTimeout(dur time.Duration) Option {
	return func(opts *options.ClientOptions) error {
		opts.SetConnectTimeout(dur)
		return nil
	}
}


<|start_filename|>tracing/req_tracer_test.go<|end_filename|>
package tracing

import (
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"

	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/mocktracer"
	logtest "github.com/sirupsen/logrus/hooks/test"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
)

func TestTracerLogging(t *testing.T) {
	rec := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "http://whatever.com/something", nil)

	log, hook := logtest.NewNullLogger()

	_, r, rt := NewTracer(rec, req, log, t.Name(), "some_resource")

	rt.Start()
	e := hook.LastEntry()
	assert.Equal(t, 5, len(e.Data))
	assert.NotEmpty(t, e.Data["request_id"])
	assert.NotEmpty(t, e.Data["remote_addr"])
	assert.Empty(t, e.Data["referrer"])
	assert.NotEmpty(t, e.Data["method"])
	assert.Equal(t, "http://whatever.com/something", e.Data["url"])

	_ = SetLogField(r, "first", "second")
	SetFinalField(r, "final", "line").Info("should have the final here")
	e = hook.LastEntry()
	assert.Equal(t, 3, len(e.Data))
	assert.NotEmpty(t, e.Data["request_id"])
	assert.Equal(t, "line", e.Data["final"])
	assert.Equal(t, "second", e.Data["first"])

	rt.Info("Shouldn't have the final line")
	e = hook.LastEntry()
	assert.Equal(t, 2, len(e.Data))
	assert.NotEmpty(t, e.Data["request_id"])
	assert.Equal(t, "second", e.Data["first"])

	rt.WriteHeader(http.StatusOK)
	rt.Write([]byte{0, 1, 2, 3})
	rt.Finish()
	e = hook.LastEntry()

	assert.Equal(t, 9, len(e.Data))

	// the automatic fields
	assert.NotEmpty(t, e.Data["dur"])
	assert.NotEmpty(t, e.Data["dur_ns"])
	assert.NotEmpty(t, e.Data["request_id"])
	assert.Equal(t, 4, e.Data["rsp_bytes"])
	assert.Equal(t, 200, e.Data["status_code"])
	assert.Equal(t, "http://whatever.com/something", e.Data["url"])
	assert.Equal(t, "GET", e.Data["method"])

	// the value that we added above
	assert.Equal(t, "second", e.Data["first"])
	assert.Equal(t, "line", e.Data["final"])
}

func TestTracerSpans(t *testing.T) {
	rec := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "http://whatever.com/something", nil)

	log, _ := logtest.NewNullLogger()

	mt := mocktracer.New()
	opentracing.SetGlobalTracer(mt)
	_, _, rt := NewTracer(rec, req, log, t.Name(), "some_resource")
	rt.Start()
	rt.WriteHeader(http.StatusOK)
	rt.Finish()

	require.Len(t, mt.FinishedSpans(), 1)
	span := mt.FinishedSpans()[0]
	assert.Equal(t, t.Name(), span.Tag(ext.ServiceName))
	assert.Equal(t, "some_resource", span.Tag(ext.ResourceName))
	assert.Equal(t, http.MethodGet, span.Tag(ext.HTTPMethod))
	assert.Equal(t, strconv.Itoa(http.StatusOK), span.Tag(ext.HTTPCode))
	assert.Equal(t, rt.RequestID, span.Tag("http.request_id"))
}


<|start_filename|>trafficmesh/signature.go<|end_filename|>
package trafficmesh

import (
	"errors"
	"fmt"
	"net/http"
	"net/url"

	"github.com/dgrijalva/jwt-go"
)

const signatureHeader = "X-NF-Mesh-Signature"

// SignatureDecoder decodes a signed traffic-mesh header.
type SignatureDecoder struct {
	secret string
}

// SignaturePayload represents the fields in a traffic-mesh signature.
type SignaturePayload struct {
	jwt.StandardClaims
	SiteID    string `json:"sid,omitempty"`
	DeployID  string `json:"did,omitempty"`
	AccountID string `json:"aid,omitempty"`
	URL       string `json:"url,omitempty"`
	Remapped  bool   `json:"remapped,omitempty"`
}

// NewSignatureDecoder constructs a new SignatureDecoder. When secret is an empty string,
// DecodeSignature is a no-op.
func NewSignatureDecoder(secret string) *SignatureDecoder {
	return &SignatureDecoder{
		secret: secret,
	}
}

// DecodeSignature decodes a traffic-mesh signature. When either the secret or the header is an
// empty string, this method returns a nil payload and nil error.
func (d *SignatureDecoder) DecodeSignature(req *http.Request) (*SignaturePayload, error) {
	if d.secret == "" || req.Header.Get(signatureHeader) == "" {
		return nil, nil
	}

	payload := new(SignaturePayload)
	token, err := jwt.ParseWithClaims(req.Header.Get(signatureHeader), payload, func(token *jwt.Token) (interface{}, error) {
		alg, ok := token.Method.(*jwt.SigningMethodHMAC)
		if !ok || alg != jwt.SigningMethodHS256 {
			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(d.secret), nil
	})
	if err != nil {
		return nil, fmt.Errorf("failed to decode traffic mesh signature: %w", err)
	}
	if !token.Valid {
		return nil, errors.New("invalid token")
	}

	payloadURL, err := url.Parse(payload.URL)
	if err != nil {
		return nil, err
	}
	if payloadURL.Host != req.Host {
		return nil, fmt.Errorf("token host %s doesn't match request host: %s", payloadURL.Host, req.Host)
	}
	if payloadURI, reqURI := payloadURL.RequestURI(), req.URL.RequestURI(); payloadURI != reqURI {
		return nil, fmt.Errorf("token uri %s doesn't match request uri: %s", payloadURI, reqURI)
	}

	return payload, nil
}


<|start_filename|>mongoclient/db.go<|end_filename|>
package mongoclient

import (
	"context"
	"strings"
	"time"

	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"

	"github.com/sirupsen/logrus"

	"github.com/netlify/netlify-commons/nconf"
)

const (
	CollectionBlobs         = "blobs"
	CollectionResellers     = "resellers"
	CollectionUsers         = "users"
	CollectionSubscriptions = "bb_subscriptions"
	CollectionSites         = "projects"
)

type Auth struct {
	Username string
	Password string
	Source   string
}

type Config struct {
	AppName            string
	TLS                *nconf.TLSConfig
	Servers            []string
	ReplSetName        string
	ConnTimeout        time.Duration
	Auth               *Auth
	SecondaryPreferred bool
}

func ConnectWithOptions(log logrus.FieldLogger, replSet string, servers []string, opts ...Option) (*mongo.Client, error) {
	finalOpts := options.Client().
		SetConnectTimeout(time.Minute). // use a default
		SetReplicaSet(replSet).
		SetHosts(servers)
	for _, opt := range opts {
		if err := opt(finalOpts); err != nil {
			return nil, err
		}
	}
	log.WithFields(logrus.Fields{
		"servers":     strings.Join(finalOpts.Hosts, ","),
		"replica_set": finalOpts.ReplicaSet,
	}).Debug("Dialing database")

	client, err := mongo.Connect(context.Background(), finalOpts)
	if err != nil {
		return nil, err
	}

	// Connect does not block for server discovery, so we should ping to ensure
	// we are actually connected
	ctx, cancel := context.WithTimeout(context.Background(), *finalOpts.ConnectTimeout+time.Second)
	defer cancel()

	if err := client.Ping(ctx, readpref.Primary()); err != nil {
		log.WithError(err).Error("Failed to ping primary Mongo instance")
		return nil, err
	}

	log.Debug("Connected to MongoDB")
	return client, nil
}

// Connect connects to MongoDB
func Connect(log logrus.FieldLogger, config *Config) (*mongo.Client, error) {
	opts := []Option{
		AppName(config.AppName),
	}
	if config.TLS != nil {
		opts = append(opts, TLSOption(log, *config.TLS))
	}
	if config.Auth != nil {
		opts = append(opts, AuthOption(*config.Auth))
	}
	if config.SecondaryPreferred {
		opts = append(opts, SecondaryPreferred())
	}

	return ConnectWithOptions(log, config.ReplSetName, config.Servers, opts...)
}


<|start_filename|>nconf/configuration.go<|end_filename|>
package nconf

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/joho/godotenv"
	"github.com/kelseyhightower/envconfig"
	"github.com/netlify/netlify-commons/featureflag"
	"github.com/netlify/netlify-commons/instrument"
	"github.com/netlify/netlify-commons/metriks"
	"github.com/netlify/netlify-commons/tracing"
	"github.com/pkg/errors"
	"github.com/spf13/viper"
	"gopkg.in/yaml.v3"
)

// ErrUnknownConfigFormat indicates the extension of the config file is not supported as a config source
type ErrUnknownConfigFormat struct {
	ext string
}

func (e *ErrUnknownConfigFormat) Error() string {
	return fmt.Sprintf("Unknown config format: %s", e.ext)
}

type RootConfig struct {
	Log         LoggingConfig
	BugSnag     *BugSnagConfig
	Metrics     metriks.Config
	Tracing     tracing.Config
	FeatureFlag featureflag.Config
	Instrument  instrument.Config
}

func DefaultConfig() RootConfig {
	return RootConfig{
		Log: LoggingConfig{
			QuoteEmptyFields: true,
		},
		Tracing: tracing.Config{
			Host: "localhost",
			Port: "8126",
		},
		Metrics: metriks.Config{
			Host: "localhost",
			Port: 8125,
		},
	}
}

/*
 Deprecated: This method relies on parsing the json/yaml to a map, then running it through mapstructure.
 This required that both tags exist (annoying!). And so there is now LoadConfigFromFile.
*/
// LoadFromFile will load the configuration from the specified file based on the file type
// There is only support for .json and .yml now
func LoadFromFile(configFile string, input interface{}) error {
	if configFile == "" {
		return nil
	}

	switch {
	case strings.HasSuffix(configFile, ".json"):
		viper.SetConfigType("json")
	case strings.HasSuffix(configFile, ".yaml"):
		fallthrough
	case strings.HasSuffix(configFile, ".yml"):
		viper.SetConfigType("yaml")
	}
	viper.SetConfigFile(configFile)

	if err := viper.ReadInConfig(); err != nil && !os.IsNotExist(err) {
		_, ok := err.(viper.ConfigFileNotFoundError)
		if !ok {
			return errors.Wrap(err, "reading configuration from files")
		}
	}

	return viper.Unmarshal(input)
}

// LoadConfigFromFile will load the configuration from the specified file based on the file type
// There is only support for .json and .yml now. It will use the underlying json/yaml packages directly.
// meaning those should be the only required tags.
func LoadConfigFromFile(configFile string, input interface{}) error {
	if configFile == "" {
		return nil
	}

	// read in all the bytes
	data, err := ioutil.ReadFile(configFile)
	if err != nil {
		return err
	}

	configExt := filepath.Ext(configFile)
	switch configExt {
	case ".json":
		return json.Unmarshal(data, input)
	case ".yaml", ".yml":
		return yaml.Unmarshal(data, input)
	}
	return &ErrUnknownConfigFormat{configExt}
}

func LoadFromEnv(prefix, filename string, face interface{}) error {
	var err error
	if filename == "" {
		err = godotenv.Load()
		if os.IsNotExist(err) {
			err = nil
		}
	} else {
		err = godotenv.Load(filename)
	}

	if err != nil {
		return err
	}

	return envconfig.Process(prefix, face)
}


<|start_filename|>nconf/tls.go<|end_filename|>
package nconf

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"

	"github.com/pkg/errors"
)

type TLSConfig struct {
	CAFiles  []string `mapstructure:"ca_files" envconfig:"ca_files" json:"ca_files" yaml:"ca_files"`
	KeyFile  string   `mapstructure:"key_file" split_words:"true" json:"key_file" yaml:"key_file"`
	CertFile string   `mapstructure:"cert_file" split_words:"true" json:"cert_file" yaml:"cert_file"`

	Cert string `mapstructure:"cert"`
	Key  string `mapstructure:"key"`
	CA   string `mapstructure:"ca"`

	Insecure bool `default:"false"`
	Enabled  bool `default:"false"`
}

func (cfg TLSConfig) TLSConfig() (*tls.Config, error) {
	var err error

	tlsConf := &tls.Config{
		MinVersion:         tls.VersionTLS12,
		InsecureSkipVerify: cfg.Insecure,
	}

	// Load CA
	if cfg.CA != "" {
		tlsConf.RootCAs, err = LoadCAFromValue(cfg.CA)
	} else if len(cfg.CAFiles) > 0 {
		tlsConf.RootCAs, err = LoadCAFromFiles(cfg.CAFiles)
	} else {
		tlsConf.RootCAs, err = x509.SystemCertPool()
	}

	if err != nil {
		return nil, errors.Wrap(err, "Error setting up Root CA pool")
	}

	// Load Certs if any
	var cert tls.Certificate
	if cfg.Cert != "" && cfg.Key != "" {
		cert, err = LoadCertFromValues(cfg.Cert, cfg.Key)
		tlsConf.Certificates = append(tlsConf.Certificates, cert)
	} else if cfg.CertFile != "" && cfg.KeyFile != "" {
		cert, err = LoadCertFromFiles(cfg.CertFile, cfg.KeyFile)
		tlsConf.Certificates = append(tlsConf.Certificates, cert)
	}

	if err != nil {
		return nil, errors.Wrap(err, "Error loading certificate KeyPair")
	}

	// Backwards compatibility: if TLS is not explicitly enabled, return nil if no certificate was provided
	// Old code disabled TLS by not providing a certificate, which returned nil when calling TLSConfig()
	if !cfg.Enabled && len(tlsConf.Certificates) == 0 {
		return nil, nil
	}

	return tlsConf, nil
}

func LoadCertFromValues(certPEM, keyPEM string) (tls.Certificate, error) {
	return tls.X509KeyPair([]byte(certPEM), []byte(keyPEM))
}

func LoadCertFromFiles(certFile, keyFile string) (tls.Certificate, error) {
	return tls.LoadX509KeyPair(certFile, keyFile)
}

func LoadCAFromFiles(cafiles []string) (*x509.CertPool, error) {
	pool := x509.NewCertPool()

	for _, caFile := range cafiles {
		caData, err := ioutil.ReadFile(caFile)
		if err != nil {
			return nil, err
		}

		if !pool.AppendCertsFromPEM(caData) {
			return nil, fmt.Errorf("Failed to add CA cert at %s", caFile)
		}
	}

	return pool, nil
}

func LoadCAFromValue(ca string) (*x509.CertPool, error) {
	pool := x509.NewCertPool()
	if !pool.AppendCertsFromPEM([]byte(ca)) {
		return nil, fmt.Errorf("Failed to add CA cert")
	}
	return pool, nil
}


<|start_filename|>testutil/logger.go<|end_filename|>
package testutil

import (
	"os"
	"testing"

	"github.com/sirupsen/logrus"
	"github.com/sirupsen/logrus/hooks/test"
)

// Opt is a function that will modify the logger used
type Opt func(l *logrus.Logger)

// TL will build and return a test logger
func TL(t *testing.T, opts ...Opt) *logrus.Entry {
	l, _ := TestLogger(t, opts...)
	return l
}

// TestLogger will build a logger that is useful for debugging
// it respects levels configured by the 'LOG_LEVEL' env var.
// It takes opt functions to modify the logger used
func TestLogger(t *testing.T, opts ...Opt) (*logrus.Entry, *test.Hook) {
	l := logrus.New()
	l.SetOutput(testLogWrapper{t})
	hook := test.NewLocal(l)
	l.SetReportCaller(true)
	if ll := os.Getenv("LOG_LEVEL"); ll != "" {
		level, err := logrus.ParseLevel(ll)
		if err != nil {
			t.Logf("Error parsing the log level env var (%s), defaulting to info", ll)
			level = logrus.InfoLevel
		}
		l.SetLevel(level)
	}

	for _, o := range opts {
		o(l)
	}

	return l.WithField("test", t.Name()), hook
}

type testLogWrapper struct {
	t *testing.T
}

func (w testLogWrapper) Write(p []byte) (n int, err error) {
	w.t.Log(string(p))
	return len(p), nil
}

// OptSetLevel will override the env var used to configre the logger
func OptSetLevel(lvl logrus.Level) Opt {
	return func(l *logrus.Logger) {
		l.SetLevel(lvl)
	}
}

// OptReportCaller will override the reporting of the calling function info
func OptReportCaller(b bool) Opt {
	return func(l *logrus.Logger) {
		l.SetReportCaller(b)
	}
}


<|start_filename|>server/options.go<|end_filename|>
package server

import (
	"crypto/tls"
	"fmt"
	"net/http"
	"time"
)

// Opt will allow modification of the http server
type Opt func(s *http.Server)

// WithWriteTimeout will override the server's write timeout
func WithWriteTimeout(dur time.Duration) Opt {
	return func(s *http.Server) {
		s.WriteTimeout = dur
	}
}

// WithReadTimeout will override the server's read timeout
func WithReadTimeout(dur time.Duration) Opt {
	return func(s *http.Server) {
		s.ReadTimeout = dur
	}
}

// WithTLS will use the provided TLS configuration
func WithTLS(cfg *tls.Config) Opt {
	return func(s *http.Server) {
		s.TLSConfig = cfg
	}
}

// WithAddress will set the address field on the server
func WithAddress(addr string) Opt {
	return func(s *http.Server) {
		s.Addr = addr
	}
}

// WithHostAndPort will use them in the form host:port as the address field on the server
func WithHostAndPort(host string, port int) Opt {
	return WithAddress(fmt.Sprintf("%s:%d", host, port))
}


<|start_filename|>util/url_test.go<|end_filename|>
package util

import (
	"encoding/json"
	"net/url"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/yaml.v3"
)

func TestURL_Unmarshal(t *testing.T) {
	testCases := []struct {
		name      string
		url       string
		expected  *url.URL
		errCheck  require.ErrorAssertionFunc
		unmarshal func([]byte, interface{}) error
	}{
		{"empty-json", `{"u": ""}`, nil, require.Error, json.Unmarshal},
		{"invalid-json", `{"u": "<>:hey>"}`, nil, require.Error, json.Unmarshal},
		{"valid-json", `{"u": "https://netlify.com"}`, &url.URL{Scheme: "https", Host: "netlify.com"}, require.NoError, json.Unmarshal},

		{"empty-yaml", `u: ""}`, nil, require.Error, yaml.Unmarshal},
		{"invalid-yaml", `u: "<>:hey>"}`, nil, require.Error, yaml.Unmarshal},
		{"valid-yaml", `{u: https://netlify.com}`, &url.URL{Scheme: "https", Host: "netlify.com"}, require.NoError, yaml.Unmarshal},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			var s struct {
				U URL `json:"u"`
			}
			tc.errCheck(t, tc.unmarshal([]byte(tc.url), &s))
			assert.Equal(t, tc.expected, s.U.URL)
		})
	}
}

func TestURL_Marshal(t *testing.T) {
	testCases := []struct {
		name     string
		marshal  func(interface{}) ([]byte, error)
		expected string
	}{
		{"json", json.Marshal, `{"u":"https://netlify.com"}`},
		{"yaml", yaml.Marshal, "u: https://netlify.com\n"},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			u := struct {
				U URL `json:"u" yaml:"u"`
			}{U: URL{&url.URL{Scheme: "https", Host: "netlify.com"}}}

			serialized, err := tc.marshal(u)
			require.NoError(t, err)
			assert.Equal(t, tc.expected, string(serialized))
		})
	}
}


<|start_filename|>router/router.go<|end_filename|>
package router

import (
	"net/http"
	"strings"

	"github.com/netlify/netlify-commons/tracing"
	"github.com/rs/cors"

	"github.com/go-chi/chi"
	"github.com/sebest/xff"
	"github.com/sirupsen/logrus"
)

type chiWrapper struct {
	chi chi.Router

	version       string
	svcName       string
	tracingPrefix string
	rootLogger    logrus.FieldLogger

	healthEndpoint string
	healthHandler  APIHandler

	enableTracing bool
	enableCORS    bool
	enableRecover bool
}

// Router wraps the chi router to make it slightly more accessible
type Router interface {
	// Use appends one middleware onto the Router stack.
	Use(fn Middleware)

	// With adds an inline middleware for an endpoint handler.
	With(fn Middleware) Router

	// Route mounts a sub-Router along a `pattern`` string.
	Route(pattern string, fn func(r Router))

	// Method adds a routes for a `pattern` that matches the `method` HTTP method.
	Method(method, pattern string, h APIHandler)

	// HTTP-method routing along `pattern`
	Delete(pattern string, h APIHandler)
	Get(pattern string, h APIHandler)
	Post(pattern string, h APIHandler)
	Put(pattern string, h APIHandler)

	// Mount attaches another http.Handler along ./pattern/*
	Mount(pattern string, h http.Handler)

	ServeHTTP(http.ResponseWriter, *http.Request)
}

// New creates a router with sensible defaults (xff, request id, cors)
func New(log logrus.FieldLogger, options ...Option) Router {
	r := &chiWrapper{
		chi:        chi.NewRouter(),
		version:    "unknown",
		rootLogger: log,
	}

	xffmw, _ := xff.Default()
	r.Use(xffmw.Handler)
	for _, opt := range options {
		opt(r)
	}

	if r.enableRecover {
		r.Use(Recoverer(log))
	}
	r.Use(VersionHeader(r.svcName, r.version))
	if r.enableCORS {
		corsMiddleware := cors.New(cors.Options{
			AllowedMethods:   []string{"GET", "POST", "PATCH", "PUT", "DELETE"},
			AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type"},
			ExposedHeaders:   []string{"Link", "X-Total-Count"},
			AllowCredentials: true,
		})
		r.Use(corsMiddleware.Handler)
	}

	if r.healthEndpoint != "" {
		r.Use(HealthCheck(r.healthEndpoint, r.healthHandler))
	}

	return r
}

// Route allows creating a generic route
func (r *chiWrapper) Route(pattern string, fn func(Router)) {
	r.chi.Route(pattern, func(c chi.Router) {
		wrapper := new(chiWrapper)
		*wrapper = *r
		wrapper.chi = c
		wrapper.tracingPrefix = sanitizePattern(pattern)
		fn(wrapper)
	})
}

// Method adds a routes for a `pattern` that matches the `method` HTTP method.
func (r *chiWrapper) Method(method, pattern string, h APIHandler) {
	r.chi.Method(method, pattern, r.traceRequest(method, pattern, h))
}

// Get adds a GET route
func (r *chiWrapper) Get(pattern string, fn APIHandler) {
	r.chi.Get(pattern, r.traceRequest(http.MethodGet, pattern, fn))
}

// Post adds a POST route
func (r *chiWrapper) Post(pattern string, fn APIHandler) {
	r.chi.Post(pattern, r.traceRequest(http.MethodPost, pattern, fn))
}

// Put adds a PUT route
func (r *chiWrapper) Put(pattern string, fn APIHandler) {
	r.chi.Put(pattern, r.traceRequest(http.MethodPut, pattern, fn))
}

// Delete adds a DELETE route
func (r *chiWrapper) Delete(pattern string, fn APIHandler) {
	r.chi.Delete(pattern, r.traceRequest(http.MethodDelete, pattern, fn))
}

// WithBypass adds an inline chi middleware for an endpoint handler
func (r *chiWrapper) With(fn Middleware) Router {
	r.chi = r.chi.With(fn)
	return r
}

// UseBypass appends one chi middleware onto the Router stack
func (r *chiWrapper) Use(fn Middleware) {
	r.chi.Use(fn)
}

// ServeHTTP will serve a request
func (r *chiWrapper) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	r.chi.ServeHTTP(w, req)
}

// Mount attaches another http.Handler along ./pattern/*
func (r *chiWrapper) Mount(pattern string, h http.Handler) {
	if r.enableTracing {
		h = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			tracing.TrackRequest(w, req, r.rootLogger, r.svcName, pattern, h)
		})
	}
	r.chi.Mount(pattern, h)
}

// =======================================
// HTTP handler with custom error payload
// =======================================

type APIHandler func(w http.ResponseWriter, r *http.Request) error

func HandlerFunc(fn APIHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if err := fn(w, r); err != nil {
			HandleError(err, w, r)
		}
	}
}

func (r *chiWrapper) traceRequest(method, pattern string, fn APIHandler) http.HandlerFunc {
	f := HandlerFunc(fn)
	if r.enableTracing {
		pattern = sanitizePattern(pattern)
		if r.tracingPrefix != "" {
			pattern = r.tracingPrefix + "." + pattern
		}

		resourceName := strings.ToUpper(method)
		if pattern != "" {
			resourceName += "::" + pattern
		}

		return func(w http.ResponseWriter, req *http.Request) {
			tracing.TrackRequest(w, req, r.rootLogger, r.svcName, resourceName, f)
		}
	}
	return f
}

func sanitizePattern(pattern string) string {
	pattern = strings.TrimPrefix(pattern, "/")
	pattern = strings.ReplaceAll(pattern, "{", "")
	pattern = strings.ReplaceAll(pattern, "}", "")
	pattern = strings.ReplaceAll(pattern, "/", ".")
	pattern = strings.TrimSuffix(pattern, ".")
	return pattern
}


<|start_filename|>nconf/events.go<|end_filename|>
package nconf

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"

	ddstatsd "github.com/DataDog/datadog-go/statsd"
	"github.com/netlify/netlify-commons/metriks"
	"github.com/pkg/errors"
)

func sendDatadogEvents(conf metriks.Config, serviceName, version string) error {
	if !conf.Enabled {
		return nil
	}

	client, err := ddstatsd.New(conf.StatsdAddr())
	if err != nil {
		return errors.Wrap(err, "failed to connect to datadog agent")
	}

	tags := []string{
		fmt.Sprintf("version:%s", version),
		fmt.Sprintf("service:%s", serviceName),
	}
	host, err := os.Hostname()
	if err != nil {
		return errors.Wrap(err, "failed to get the hostname")
	}

	key := "hostname"
	if val := os.Getenv("KUBERNETES_PORT"); val != "" {
		key = "pod"
	}
	tags = append(tags, fmt.Sprintf("%s:%s", key, host))

	start := &ddstatsd.Event{
		Tags:  append(tags, "event_type:startup"),
		Title: fmt.Sprintf("Service Start: %s", serviceName),
		Text:  fmt.Sprintf("Service %s @ %s is starting", serviceName, version),
	}
	if err := client.Event(start); err != nil {
		return errors.Wrap(err, "failed to send startup event")
	}

	done := &ddstatsd.Event{
		Tags:  append(tags, "event_type:shutdown"),
		Title: fmt.Sprintf("Service Shutdown: %s", serviceName),
		Text:  fmt.Sprintf("Service '%s @ %s' is stopping", serviceName, version),
	}
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)

	go func() {
		<-signals
		_ = client.Event(done)
	}()

	return nil
}


<|start_filename|>testutil/logger_test.go<|end_filename|>
package testutil

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestLoggerWrapper(t *testing.T) {
	tl, h := TestLogger(t)
	tl.Info("this is a test")
	assert.Len(t, h.Entries, 1)
	assert.Equal(t, "this is a test", h.Entries[0].Message)
	assert.Equal(t, "TestLoggerWrapper", h.Entries[0].Data["test"])
}

func TestLogWrapperLevel(t *testing.T) {
	testCases := []struct {
		desc     string
		expected []string
	}{
		{desc: "DEBUG", expected: []string{"debug", "info", "warn", "error"}},
		{desc: "NONSENSE", expected: []string{"info", "warn", "error"}},
		{desc: "INFO", expected: []string{"info", "warn", "error"}},
		{desc: "ERROR", expected: []string{"error"}},
	}
	for _, tC := range testCases {
		t.Run(tC.desc, func(t *testing.T) {
			os.Setenv("LOG_LEVEL", tC.desc)
			tl, h := TestLogger(t)
			tl.Debug("debug")
			tl.Info("info")
			tl.Warn("warn")
			tl.Error("error")

			logged := []string{}
			for _, entry := range h.Entries {
				logged = append(logged, entry.Message)
			}
			assert.Equal(t, tC.expected, logged)
		})
	}
}


<|start_filename|>metriks/distribution.go<|end_filename|>
package metriks

import (
	"fmt"
	"strings"

	"github.com/DataDog/datadog-go/statsd"
	"github.com/armon/go-metrics"
)

var (
	distributionFunc         = func(name string, value float64, tags ...metrics.Label) {}
	distributionErrorHandler = func(_ error) {}
)

func initDistribution(url string, serviceName string, permTags []string) error {
	statsd, err := statsd.New(url)
	if err != nil {
		return err
	}

	distributionFunc = func(name string, value float64, tags ...metrics.Label) {
		ddtags := make([]string, 0, len(permTags)+len(tags))
		ddtags = append(ddtags, permTags...)
		for _, v := range tags {
			ddtags = append(ddtags, fmt.Sprintf("%s:%s", v.Name, v.Value))
		}

		name = fmt.Sprintf("%s.%s", serviceName, strings.ReplaceAll(name, "-", "_"))

		err := statsd.Distribution(name, float64(value), ddtags, 1)
		if err != nil {
			distributionErrorHandler(err)
		}
	}

	return nil
}

// SetDistributionErrorHandler will set the global error handler. It will be invoked
// anytime that the statsd call produces an error
func SetDistributionErrorHandler(f func(error)) {
	distributionErrorHandler = f
}

// Distribution will report the value as a distribution metric to datadog.
// it only makes sense when you're using the datadog sink
func Distribution(name string, value float64, tags ...metrics.Label) {
	distributionFunc(name, value, tags...)
}


<|start_filename|>util/duration.go<|end_filename|>
package util

import (
	"encoding/json"
	"errors"
	"time"
)

// Duration is a serializable version version of a time.Duration
// it supports setting in yaml & json via:
// - string: 10s
// - float32/64, int/32/64: 10 (nanoseconds)
type Duration struct {
	time.Duration
}

func (d Duration) MarshalYAML() (interface{}, error) {
	return d.String(), nil
}

func (d Duration) MarshalJSON() ([]byte, error) {
	return json.Marshal(d.String())
}

func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var v interface{}
	if err := unmarshal(&v); err != nil {
		return err
	}
	return d.setValue(v)
}

func (d *Duration) UnmarshalJSON(b []byte) error {
	var v interface{}
	if err := json.Unmarshal(b, &v); err != nil {
		return err
	}
	return d.setValue(v)
}

func (d *Duration) UnmarshalText(text []byte) error {
	return d.setValue(string(text))
}

func (d *Duration) setValue(v interface{}) error {
	switch value := v.(type) {
	case float64:
		d.Duration = time.Duration(value)
	case float32:
		d.Duration = time.Duration(value)
	case int:
		d.Duration = time.Duration(value)
	case int32:
		d.Duration = time.Duration(value)
	case int64:
		d.Duration = time.Duration(value)
	case string:
		var err error
		d.Duration, err = time.ParseDuration(value)
		if err != nil {
			return err
		}
	default:
		return errors.New("invalid duration")
	}
	return nil
}


<|start_filename|>util/url.go<|end_filename|>
package util

import (
	"encoding/json"
	"fmt"
	"net/url"

	"github.com/pkg/errors"
)

// URL is a serializable version version of a url.URL
// it supports serialization in yaml and json
type URL struct {
	*url.URL
}

func (u URL) MarshalYAML() (interface{}, error) {
	return u.String(), nil
}

func (u URL) MarshalJSON() ([]byte, error) {
	return json.Marshal(u.String())
}

func (u *URL) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var v interface{}
	if err := unmarshal(&v); err != nil {
		return err
	}
	return u.setValue(v)
}

func (u *URL) UnmarshalJSON(b []byte) error {
	var v interface{}
	if err := json.Unmarshal(b, &v); err != nil {
		return err
	}
	return u.setValue(v)
}

func (u *URL) setValue(v interface{}) error {
	switch value := v.(type) {
	case string:
		if value == "" {
			return errors.New("empty string provided as url")
		}

		parsed, err := url.Parse(value)
		if err != nil {
			return errors.Wrap(err, "invalid url provided")
		}

		u.URL = parsed
	default:
		return errors.New(fmt.Sprintf("invalid type provided as url: %T", v))
	}
	return nil
}


<|start_filename|>instrument/config.go<|end_filename|>
package instrument

type Config struct {
	// Write Key for the Segment source
	Key string `json:"key" yaml:"key"`
	// If this is false, instead of sending the event to Segment, emits verbose log to logger
	Enabled bool `json:"enabled" yaml:"enabled" default:"false"`
}


<|start_filename|>instrument/doc.go<|end_filename|>
/*
Package instrument provides the tool to emit the events to the instrumentation
destination. Currently it supports Segment or logger as a destination.

When enabling, make sure to follow the guideline specified in https://github.com/netlify/segment-events

In the config file, you can define the API key, as well as if it's enabled (use
Segment) or not (use logger).
	INSTRUMENT_ENABLED=true
	INSTRUMENT_KEY=segment_api_key

To use, you can import this package:
	import "github.com/netlify/netlify-commons/instrument"

You will likely need to import the Segment's analytics package as well, to
create new traits and properties.
	import "gopkg.in/segmentio/analytics-go.v3"

Then call the functions:
	instrument.Track("userid", "service:my_event", analytics.NewProperties().Set("color", "green"))

For testing, you can create your own mock instrument and use it:
	func TestSomething (t *testing.T) {
		log := testutil.TL(t)
		old := instrument.GetGlobalClient()
		t.Cleanup(func(){ instrument.SetGlobalClient(old) })
		instrument.SetGlobalClient(instrument.MockClient{Logger: log})
	}
*/
package instrument


<|start_filename|>nconf/logging.go<|end_filename|>
package nconf

import (
	"os"
	"time"

	"github.com/sirupsen/logrus"
)

type LoggingConfig struct {
	Level            string                 `mapstructure:"log_level"`
	File             string                 `mapstructure:"log_file"`
	DisableColors    bool                   `mapstructure:"disable_colors" split_words:"true" json:"disable_colors" yaml:"disable_colors"`
	QuoteEmptyFields bool                   `mapstructure:"quote_empty_fields" split_words:"true" json:"quote_empty_fields" yaml:"quote_empty_fields"`
	TSFormat         string                 `mapstructure:"ts_format" json:"ts_format" yaml:"ts_format"`
	Fields           map[string]interface{} `mapstructure:"fields"`
	UseNewLogger     bool                   `mapstructure:"use_new_logger" split_words:"true" json:"use_new_logger" yaml:"use_new_logger"`
}

func ConfigureLogging(config LoggingConfig) (*logrus.Entry, error) {
	logger := logrus.New()

	tsFormat := time.RFC3339Nano
	if config.TSFormat != "" {
		tsFormat = config.TSFormat
	}
	// always use the full timestamp
	logger.SetFormatter(&logrus.TextFormatter{
		FullTimestamp:    true,
		DisableTimestamp: false,
		TimestampFormat:  tsFormat,
		DisableColors:    config.DisableColors,
		QuoteEmptyFields: config.QuoteEmptyFields,
	})

	// use a file if you want
	if config.File != "" {
		f, errOpen := os.OpenFile(config.File, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0664)
		if errOpen != nil {
			return nil, errOpen
		}
		logger.SetOutput(f)
		logger.Infof("Set output file to %s", config.File)
	}

	if config.Level != "" {
		level, err := logrus.ParseLevel(config.Level)
		if err != nil {
			return nil, err
		}
		logger.SetLevel(level)
		logger.Debug("Set log level to: " + logger.GetLevel().String())
	}

	f := logrus.Fields{}
	for k, v := range config.Fields {
		f[k] = v
	}

	return logger.WithFields(f), nil
}


<|start_filename|>instrument/mock.go<|end_filename|>
package instrument

import (
	"github.com/sirupsen/logrus"
	"gopkg.in/segmentio/analytics-go.v3"
)

type MockClient struct {
	Logger logrus.FieldLogger
}

var _ Client = MockClient{}

func (c MockClient) Identify(userID string, traits analytics.Traits) error {
	c.Logger.WithFields(logrus.Fields{
		"user_id": userID,
		"traits":  traits,
	}).Infof("Received Identity event")
	return nil
}

func (c MockClient) Track(userID string, event string, properties analytics.Properties) error {
	c.Logger.WithFields(logrus.Fields{
		"user_id":    userID,
		"event":      event,
		"properties": properties,
	}).Infof("Received Track event")
	return nil
}

func (c MockClient) Page(userID string, name string, properties analytics.Properties) error {
	c.Logger.WithFields(logrus.Fields{
		"user_id":    userID,
		"name":       name,
		"properties": properties,
	}).Infof("Received Page event")
	return nil
}

func (c MockClient) Group(userID string, groupID string, traits analytics.Traits) error {
	c.Logger.WithFields(logrus.Fields{
		"user_id":  userID,
		"group_id": groupID,
		"traits":   traits,
	}).Infof("Received Group event")
	return nil
}

func (c MockClient) Alias(previousID string, userID string) error {
	c.Logger.WithFields(logrus.Fields{
		"previous_id": previousID,
		"user_id":     userID,
	}).Infof("Received Alias event")
	return nil
}


<|start_filename|>tracing/tracer.go<|end_filename|>
package tracing

import (
	"net/http"
	"strconv"

	opentracing "github.com/opentracing/opentracing-go"
	ext "github.com/opentracing/opentracing-go/ext"
	"github.com/sirupsen/logrus"
	ddtrace_ext "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
	"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/opentracer"
)

func TrackRequest(w http.ResponseWriter, r *http.Request, log logrus.FieldLogger, service, resource string, next http.Handler) {
	w, r, rt := NewTracer(w, r, log, service, resource)
	rt.Start()
	next.ServeHTTP(w, r)
	rt.Finish()
}

func WrapWithSpan(r *http.Request, reqID, service, resource string) (*http.Request, opentracing.Span) {
	span := opentracing.SpanFromContext(r.Context())
	if span != nil {
		return r, span
	}

	clientContext, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
	span, ctx := opentracing.StartSpanFromContext(r.Context(), "http.handler",
		ext.RPCServerOption(clientContext),
		opentracer.ServiceName(service),
		opentracer.ResourceName(resource),
		opentracer.SpanType(ddtrace_ext.AppTypeWeb),
		opentracing.Tag{Key: "http.content_length", Value: strconv.FormatInt(r.ContentLength, 10)},
	)

	// datadog specific span.kind, normally "server"
	ext.Component.Set(span, "net/http")
	// "normal" is default request type until overridden
	ext.HTTPMethod.Set(span, r.Method)
	ext.HTTPUrl.Set(span, r.URL.String())
	scheme := "http"
	if r.Header.Get("X-Forwarded-Proto") == "https" {
		scheme = "https"
	}
	span.SetTag("http.base_url", scheme+"://"+r.Host)
	span.SetTag("http.request_id", reqID)
	return r.WithContext(ctx), span
}


<|start_filename|>nconf/configuration_test.go<|end_filename|>
package nconf

import (
	"encoding/json"
	"io/ioutil"
	"os"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/yaml.v3"
)

type testConfig struct {
	Hero     string
	Villian  string
	Matchups map[string]string
	Cities   []string

	ShootingLocation string `json:"shooting_location" yaml:"shooting_location" split_words:"true"`
}

func exampleConfig() testConfig {
	return testConfig{
		Hero:    "batman",
		Villian: "joker",
		Matchups: map[string]string{
			"batman":   "superman",
			"superman": "luther",
		},
		Cities: []string{"gotham", "central", "star"},
	}
}

func TestEnvLoadingNoFile(t *testing.T) {
	env := os.Environ()
	os.Clearenv()
	defer func() {
		for _, pair := range env {
			parts := strings.SplitN(pair, "=", 2)
			os.Setenv(parts[0], parts[1])
		}
	}()

	os.Setenv("TEST_VILLIAN", "joker")
	os.Setenv("TEST_HERO", "batman")
	os.Setenv("TEST_MATCHUPS", "batman:superman,superman:luther")
	os.Setenv("TEST_CITIES", "gotham,central,star")

	var results testConfig
	assert.NoError(t, LoadFromEnv("test", "", &results))
	validateConfig(t, exampleConfig(), results)
}

func TestEnvLoadingMissingFile(t *testing.T) {
	err := LoadFromEnv("test", "should-exist.env", &struct{}{})
	assert.Error(t, err)
}

func TestEnvLoadingFromFile(t *testing.T) {
	os.Clearenv()
	data := `
TEST_VILLIAN=joker
TEST_HERO=batman
TEST_MATCHUPS=batman:superman,superman:luther
TEST_CITIES=gotham,central,star
`
	filename := writeTestFile(t, "env", []byte(data))
	defer os.Remove(filename)

	var results testConfig
	assert.NoError(t, LoadFromEnv("test", filename, &results))
	validateConfig(t, exampleConfig(), results)
}

func TestFileLoadingNoFile(t *testing.T) {
	var results = testConfig{
		Hero: "flash",
	}
	var expected = testConfig{
		Hero: "flash",
	}
	require.NoError(t, LoadFromFile("", &results))
	validateConfig(t, expected, results)
}

func TestFileLoadJSON(t *testing.T) {
	expected := exampleConfig()
	bytes, err := json.Marshal(&expected)
	require.NoError(t, err)
	filename := writeTestFile(t, "json", bytes)
	defer os.Remove(filename)

	var results testConfig
	require.NoError(t, LoadConfigFromFile(filename, &results))
	validateConfig(t, expected, results)
}

func TestFileLoadYAML(t *testing.T) {
	expected := exampleConfig()
	bytes, err := yaml.Marshal(&expected)
	require.NoError(t, err)
	filename := writeTestFile(t, "yaml", bytes)
	defer os.Remove(filename)

	var results testConfig
	require.NoError(t, LoadConfigFromFile(filename, &results))
	validateConfig(t, expected, results)
}

func TestFileLoadWithSetFields(t *testing.T) {
	expected := testConfig{
		Hero: "wonder woman",
	}
	// serailize it without the villain set
	bytes, err := json.Marshal(&expected)
	require.NoError(t, err)
	filename := writeTestFile(t, "json", bytes)
	defer os.Remove(filename)

	// set a default field
	expected.Villian = "circe"
	expected.Cities = []string{"gotham"}

	var results testConfig
	require.NoError(t, LoadFromFile(filename, &results))

	// this will overwrite ALL the values
	assert.Equal(t, "", results.Villian)
	assert.Equal(t, "wonder woman", results.Hero)
	assert.Len(t, results.Cities, 0)
}

func TestEnvLoadingWithTags(t *testing.T) {
	data := `
NESTED_WITH_TAG=loaded
WITH_TAG=not-loaded
NESTED_WITHOUT_TAG=loaded
NESTED_JAMMEDTOGETHER=loaded
`
	filename := writeTestFile(t, "env", []byte(data))
	defer os.Remove(filename)

	results := struct {
		Nested struct {
			WithTag        string `envconfig:"with_tag"`
			WithoutTag     string `split_words:"true"`
			JammedTogether string
		}
	}{}

	require.NoError(t, LoadFromEnv("", filename, &results))
	assert.Equal(t, "loaded", results.Nested.WithTag)
	assert.Equal(t, "loaded", results.Nested.JammedTogether)
	assert.Equal(t, "loaded", results.Nested.WithoutTag)
}

func writeTestFile(t *testing.T, ext string, data []byte) string {
	f, err := ioutil.TempFile("", "test-*."+ext)
	require.NoError(t, err)

	ioutil.WriteFile(f.Name(), data, 0644)
	require.NoError(t, f.Close())
	return f.Name()
}

func validateConfig(t *testing.T, expected testConfig, results testConfig) {
	assert.Equal(t, expected.Hero, results.Hero)
	assert.Equal(t, expected.Villian, results.Villian)
	assert.Len(t, results.Cities, len(expected.Cities))
	for _, city := range expected.Cities {
		assert.Contains(t, results.Cities, city)
	}

	assert.Len(t, results.Matchups, len(expected.Matchups))
	for k, v := range expected.Matchups {
		assert.Equal(t, v, results.Matchups[k])
	}
}
