diff --git a/Makefile b/Makefile
index d92f538c6f2..316804d96f0 100644
--- a/Makefile
+++ b/Makefile
@@ -30,7 +30,7 @@ PKG := github.com/drud/ddev
SRC_DIRS := cmd pkg
# Version variables to replace in build
-VERSION_VARIABLES ?= DdevVersion SentryDSN
+VERSION_VARIABLES ?= DdevVersion SentryDSN SegmentKey
# These variables will be used as the default unless overridden by the make
DdevVersion ?= $(VERSION)
diff --git a/cmd/ddev/cmd/root.go b/cmd/ddev/cmd/root.go
index 0722d0d6c10..b67ecec5ca7 100644
--- a/cmd/ddev/cmd/root.go
+++ b/cmd/ddev/cmd/root.go
@@ -1,6 +1,7 @@
package cmd
import (
+ "github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/dockerutil"
"github.com/drud/ddev/pkg/globalconfig"
"github.com/drud/ddev/pkg/nodeps"
@@ -101,7 +102,7 @@ Support: https://ddev.readthedocs.io/en/stable/#support`,
if _, ok := ignores[cmd.CalledAs()]; ok {
return
}
- sentryNotSetupWarning()
+ instrumentationNotSetUpWarning()
// All this nonsense is to capture the official usage we used for this command.
// Unfortunately cobra doesn't seem to provide this easily.
@@ -113,13 +114,21 @@ Support: https://ddev.readthedocs.io/en/stable/#support`,
fullCommand = append(fullCommand, util.GetFirstWord(cmdCopy.Parent().Use))
cmdCopy = cmdCopy.Parent()
}
- uString := "Usage:"
- for i := len(fullCommand) - 1; i >= 0; i = i - 1 {
- uString = uString + " " + fullCommand[i]
+ for i := 0; i < len(fullCommand)/2; i++ {
+ j := len(fullCommand) - i - 1
+ fullCommand[i], fullCommand[j] = fullCommand[j], fullCommand[i]
}
- if globalconfig.DdevGlobalConfig.InstrumentationOptIn && version.SentryDSN != "" && nodeps.IsInternetActive() {
- _ = raven.CaptureMessageAndWait(uString, map[string]string{"severity-level": "info", "report-type": "usage"})
+ uString := strings.Join(fullCommand, " ")
+ event := ""
+ if len(fullCommand) > 1 {
+ event = fullCommand[1]
+ }
+
+ instrumentationNotSetUpWarning()
+ if globalconfig.DdevGlobalConfig.InstrumentationOptIn && version.SentryDSN != "" && nodeps.IsInternetActive() && len(fullCommand) > 1 {
+ _ = raven.CaptureMessageAndWait("Usage: "+uString, map[string]string{"severity-level": "info", "report-type": "usage"})
+ ddevapp.SendInstrumentationEvents(event)
}
},
}
@@ -151,16 +160,19 @@ func init() {
}
}
-func sentryNotSetupWarning() {
+func instrumentationNotSetUpWarning() {
if version.SentryDSN == "" && globalconfig.DdevGlobalConfig.InstrumentationOptIn {
output.UserOut.Warning("Instrumentation is opted in, but SentryDSN is not available.")
}
+ if version.SegmentKey == "" && globalconfig.DdevGlobalConfig.InstrumentationOptIn {
+ output.UserOut.Warning("Instrumentation is opted in, but SegmentKey is not available.")
+ }
}
-// checkDdevVersionAndOptInSentry() reads global config and checks to see if current version is different
+// checkDdevVersionAndOptInInstrumentation() reads global config and checks to see if current version is different
// from the last saved version. If it is, prompt to request anon ddev usage stats
// and update the info.
-func checkDdevVersionAndOptInSentry() error {
+func checkDdevVersionAndOptInInstrumentation() error {
if !output.JSONOutput && version.COMMIT != globalconfig.DdevGlobalConfig.LastUsedVersion && globalconfig.DdevGlobalConfig.InstrumentationOptIn == false && !globalconfig.DdevNoSentry {
allowStats := util.Confirm("It looks like you have a new ddev release.\nMay we send anonymous ddev usage statistics and errors?\nTo know what we will see please take a look at\nhttps://ddev.readthedocs.io/en/latest/users/cli-usage/#opt-in-usage-information\nPermission to beam up?")
if allowStats {
diff --git a/cmd/ddev/cmd/sequelpro.go b/cmd/ddev/cmd/sequelpro.go
index 63c95185311..86ff8c27d6e 100644
--- a/cmd/ddev/cmd/sequelpro.go
+++ b/cmd/ddev/cmd/sequelpro.go
@@ -10,7 +10,6 @@ import (
"runtime"
- "github.com/drud/ddev/pkg/appports"
"github.com/drud/ddev/pkg/ddevapp"
"github.com/drud/ddev/pkg/dockerutil"
"github.com/drud/ddev/pkg/output"
@@ -57,7 +56,7 @@ func handleSequelProCommand(appLocation string) (string, error) {
return "", err
}
- dbPrivatePort, err := strconv.ParseInt(appports.GetPort("db"), 10, 64)
+ dbPrivatePort, err := strconv.ParseInt(ddevapp.GetPort("db"), 10, 64)
if err != nil {
return "", err
}
diff --git a/cmd/ddev/cmd/start.go b/cmd/ddev/cmd/start.go
index a01ad4319fc..dbebe75e2e4 100644
--- a/cmd/ddev/cmd/start.go
+++ b/cmd/ddev/cmd/start.go
@@ -31,7 +31,7 @@ any directory by running 'ddev start projectname [projectname ...]'`,
}
// Look for version change and opt-in Sentry if it has changed.
- err = checkDdevVersionAndOptInSentry()
+ err = checkDdevVersionAndOptInInstrumentation()
if err != nil {
util.Failed(err.Error())
}
diff --git a/cmd/ddev/main.go b/cmd/ddev/main.go
index 1f2631d3367..89b7e87bf03 100644
--- a/cmd/ddev/main.go
+++ b/cmd/ddev/main.go
@@ -15,6 +15,6 @@ func main() {
func init() {
if !globalconfig.DdevNoSentry {
_ = raven.SetDSN(version.SentryDSN)
- ddevapp.SetRavenBaseTags()
+ ddevapp.SetInstrumentationBaseTags()
}
}
diff --git a/go.mod b/go.mod
index 5777d4cd374..494338994c5 100644
--- a/go.mod
+++ b/go.mod
@@ -6,8 +6,10 @@ require (
github.com/Masterminds/sprig v2.15.0+incompatible
github.com/aokoli/goutils v1.0.1 // indirect
github.com/aws/aws-sdk-go v1.14.31
+ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2 // indirect
github.com/cheggaaa/pb v1.0.25
+ github.com/denisbrodbeck/machineid v1.0.1
github.com/drud/go-pantheon v0.1.0
github.com/evalphobia/logrus_sentry v0.5.0
github.com/fatih/color v1.7.0
@@ -24,6 +26,7 @@ require (
github.com/imdario/mergo v0.3.5 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
+ github.com/kr/pretty v0.1.0 // indirect
github.com/lextoumbourou/goodhosts v2.1.0+incompatible
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a
github.com/magiconair/properties v1.8.0 // indirect
@@ -36,6 +39,7 @@ require (
github.com/onsi/gomega v1.4.3 // indirect
github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pkg/errors v0.8.0
+ github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect
github.com/sirupsen/logrus v1.0.6
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
@@ -46,9 +50,11 @@ require (
github.com/spf13/pflag v1.0.1 // indirect
github.com/spf13/viper v1.0.2
github.com/stretchr/testify v1.2.2
+ github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac
gopkg.in/cheggaaa/pb.v1 v1.0.26 // indirect
gopkg.in/ini.v1 v1.39.0 // indirect
+ gopkg.in/segmentio/analytics-go.v3 v3.0.1
gopkg.in/yaml.v2 v2.2.1
gotest.tools v2.2.0+incompatible // indirect
)
diff --git a/go.sum b/go.sum
index 244aad56aca..7cc6b28986c 100644
--- a/go.sum
+++ b/go.sum
@@ -14,6 +14,8 @@ github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg=
github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
github.com/aws/aws-sdk-go v1.14.31 h1:amhorvKh1zNxo9YCntvA5uDmgw+pCYXOp4xO8WS1oDg=
github.com/aws/aws-sdk-go v1.14.31/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2 h1:MmeatFT1pTPSVb4nkPmBFN/LRZ97vPjsFKsZrU3KKTs=
github.com/certifi/gocertifi v0.0.0-20180905225744-ee1a9a0726d2/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cheggaaa/pb v1.0.25 h1:tFpebHTkI7QZx1q1rWGOKhbunhZ3fMaxTvHDWn1bH/4=
@@ -22,6 +24,8 @@ github.com/containerd/continuity v0.0.0-20180814194400-c7c5070e6f6e h1:KEBqsIJcj
github.com/containerd/continuity v0.0.0-20180814194400-c7c5070e6f6e/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
+github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23 h1:mJtkfC9RUrUWHMk0cFDNhVoc9U3k2FRAzEZ+5pqSIHo=
github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -77,6 +81,11 @@ github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Ao
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lextoumbourou/goodhosts v2.1.0+incompatible h1:1U1p5Z1wrXl23/fW/GY4zdTbQ8UJbyvrkPbqAZ6tzbw=
github.com/lextoumbourou/goodhosts v2.1.0+incompatible/go.mod h1:89s48k108X3gKDWn8AHk3gUzUGTcMZCCAOsE4QU1bbo=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
@@ -111,6 +120,8 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c h1:rsRTAcCR5CeNLkvgBVSjQoDGRRt6kggsE6XYBqCv2KQ=
+github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@@ -135,6 +146,8 @@ github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCO
github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
+github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
+github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -159,6 +172,8 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNj
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/ini.v1 v1.39.0 h1:Jf2sFGT+sAd7i+4ftUN1Jz90uw8XNH8NXbbOY16taA8=
gopkg.in/ini.v1 v1.39.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/segmentio/analytics-go.v3 v3.0.1 h1:lPX/m/RnUFwu33p/1vRx4O3aexmrS6vB8OkIiXnPgAw=
+gopkg.in/segmentio/analytics-go.v3 v3.0.1/go.mod h1:4QqqlTlSSpVlWA9/9nDcPw+FkM2yv1NQoYjUbL9/JAw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
diff --git a/pkg/appports/ports.go b/pkg/appports/ports.go
index d11baf6ffa7..e1367c6e844 100644
--- a/pkg/appports/ports.go
+++ b/pkg/appports/ports.go
@@ -1,40 +1,3 @@
package appports
-import (
- "strings"
-
- "github.com/drud/ddev/pkg/util"
-)
-
// Define the DBA and MailHog ports as variables so that we can override them with ldflags if required.
-
-// DBAPort defines the default phpmyadmin port used on the router.
-var dbaPort = "8036"
-
-// mailHogPort defines the default mailhog exposed by the router.
-var mailhogPort = "8025"
-
-// dbPort defines the default DB (MySQL) port.
-var dbPort = "3306"
-
-// webPort defines the internal web port
-var webPort = "80"
-
-var ports = map[string]string{
- "mailhog": mailhogPort,
- "dba": dbaPort,
- "db": dbPort,
- "web": webPort,
-}
-
-// GetPort returns the external router (as a string) for the given service. This can be used to find a given port for docker-compose manifests,
-// or for automated testing.
-func GetPort(service string) string {
- service = strings.ToLower(service)
- val, ok := ports[service]
- if !ok {
- util.Failed("Could not find port for service %s", service)
- }
-
- return val
-}
diff --git a/pkg/ddevapp/backdrop.go b/pkg/ddevapp/backdrop.go
index 01e2c0307f6..86b3b654de7 100644
--- a/pkg/ddevapp/backdrop.go
+++ b/pkg/ddevapp/backdrop.go
@@ -9,7 +9,6 @@ import (
"io/ioutil"
- "github.com/drud/ddev/pkg/appports"
"github.com/drud/ddev/pkg/archive"
"github.com/drud/ddev/pkg/fileutil"
"github.com/drud/ddev/pkg/output"
@@ -44,7 +43,7 @@ func NewBackdropSettings(app *DdevApp) *BackdropSettings {
DatabasePassword: "db",
DatabaseHost: "db",
DatabaseDriver: "mysql",
- DatabasePort: appports.GetPort("db"),
+ DatabasePort: GetPort("db"),
DatabasePrefix: "",
HashSalt: util.RandString(64),
Signature: DdevFileSignature,
diff --git a/pkg/ddevapp/config.go b/pkg/ddevapp/config.go
index 2e2d181946b..3ed3ce75f46 100644
--- a/pkg/ddevapp/config.go
+++ b/pkg/ddevapp/config.go
@@ -22,7 +22,6 @@ import (
"runtime"
- "github.com/drud/ddev/pkg/appports"
"github.com/drud/ddev/pkg/fileutil"
"github.com/drud/ddev/pkg/output"
"github.com/drud/ddev/pkg/util"
@@ -133,7 +132,7 @@ func NewApp(AppRoot string, includeOverrides bool, provider string) (*DdevApp, e
} else {
return app, fmt.Errorf("provider '%s' is not implemented", provider)
}
- app.SetRavenTags()
+ app.SetInstrumentationAppTags()
return app, nil
}
@@ -627,9 +626,9 @@ func (app *DdevApp) RenderComposeYAML() (string, error) {
Name: app.Name,
Plugin: "ddev",
AppType: app.Type,
- MailhogPort: appports.GetPort("mailhog"),
- DBAPort: appports.GetPort("dba"),
- DBPort: appports.GetPort("db"),
+ MailhogPort: GetPort("mailhog"),
+ DBAPort: GetPort("dba"),
+ DBPort: GetPort("db"),
DdevGenerated: DdevFileSignature,
HostDockerInternalIP: hostDockerInternalIP,
ComposeVersion: version.DockerComposeFileFormatVersion,
diff --git a/pkg/ddevapp/ddevapp.go b/pkg/ddevapp/ddevapp.go
index 1def51b8411..8dd3fc72c37 100644
--- a/pkg/ddevapp/ddevapp.go
+++ b/pkg/ddevapp/ddevapp.go
@@ -23,7 +23,6 @@ import (
"time"
"github.com/drud/ddev/pkg/appimport"
- "github.com/drud/ddev/pkg/appports"
"github.com/drud/ddev/pkg/archive"
"github.com/drud/ddev/pkg/ddevhosts"
"github.com/drud/ddev/pkg/dockerutil"
@@ -196,7 +195,7 @@ func (app *DdevApp) Describe() (map[string]interface{}, error) {
dbinfo["host"] = "db"
dbPublicPort, err := app.GetPublishedPort("db")
util.CheckErr(err)
- dbinfo["dbPort"] = appports.GetPort("db")
+ dbinfo["dbPort"] = GetPort("db")
util.CheckErr(err)
dbinfo["published_port"] = dbPublicPort
dbinfo["mariadb_version"] = app.MariaDBVersion
@@ -238,7 +237,7 @@ func (app *DdevApp) GetPublishedPort(serviceName string) (int, error) {
return -1, fmt.Errorf("Failed to find container of type %s: %v", serviceName, err)
}
- privatePort, _ := strconv.ParseInt(appports.GetPort(serviceName), 10, 16)
+ privatePort, _ := strconv.ParseInt(GetPort(serviceName), 10, 16)
publishedPort := dockerutil.GetPublishedPort(privatePort, *container)
return publishedPort, nil
diff --git a/pkg/ddevapp/drupal.go b/pkg/ddevapp/drupal.go
index 8994175d98c..7c6955008fe 100755
--- a/pkg/ddevapp/drupal.go
+++ b/pkg/ddevapp/drupal.go
@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/drud/ddev/pkg/dockerutil"
- "github.com/drud/ddev/pkg/appports"
"github.com/drud/ddev/pkg/output"
"github.com/drud/ddev/pkg/util"
@@ -51,7 +50,7 @@ func NewDrupalSettings(app *DdevApp) *DrupalSettings {
DatabasePassword: "db",
DatabaseHost: "db",
DatabaseDriver: "mysql",
- DatabasePort: appports.GetPort("db"),
+ DatabasePort: GetPort("db"),
DatabasePrefix: "",
HashSalt: util.RandString(64),
Signature: DdevFileSignature,
diff --git a/pkg/ddevapp/instrumentation.go b/pkg/ddevapp/instrumentation.go
new file mode 100644
index 00000000000..9d75350beaa
--- /dev/null
+++ b/pkg/ddevapp/instrumentation.go
@@ -0,0 +1,116 @@
+package ddevapp
+
+import (
+ "fmt"
+ "github.com/denisbrodbeck/machineid"
+ "github.com/drud/ddev/pkg/globalconfig"
+ "github.com/drud/ddev/pkg/nodeps"
+ "github.com/drud/ddev/pkg/util"
+ "github.com/drud/ddev/pkg/version"
+ "github.com/getsentry/raven-go"
+ "gopkg.in/segmentio/analytics-go.v3"
+ "runtime"
+ "strconv"
+)
+
+var hashedHostID string
+
+// SetInstrumentationBaseTags sets the basic always-used tags for Sentry/Raven/Segment
+func SetInstrumentationBaseTags() {
+ if globalconfig.DdevGlobalConfig.InstrumentationOptIn {
+ dockerVersion, _ := version.GetDockerVersion()
+ composeVersion, _ := version.GetDockerComposeVersion()
+ isToolbox := nodeps.IsDockerToolbox()
+
+ raven.SetRelease("ddev@" + version.VERSION)
+
+ nodeps.InstrumentationTags["OS"] = runtime.GOOS
+ nodeps.InstrumentationTags["dockerVersion"] = dockerVersion
+ nodeps.InstrumentationTags["dockerComposeVersion"] = composeVersion
+ nodeps.InstrumentationTags["dockerToolbox"] = strconv.FormatBool(isToolbox)
+ nodeps.InstrumentationTags["version"] = version.VERSION
+ nodeps.InstrumentationTags["ServerHash"] = hashedHostID
+
+ // Add these tags to sentry/raven
+ raven.SetTagsContext(nodeps.InstrumentationTags)
+ }
+}
+
+// SetInstrumentationAppTags creates app-specific tags for Sentry/Raven/Segment
+func (app *DdevApp) SetInstrumentationAppTags() {
+ ignoredProperties := []string{"approot", "hostnames", "httpurl", "httpsurl", "mailhog_url", "name", "phpmyadmin_url", "router_status_log", "shortroot", "urls"}
+
+ if globalconfig.DdevGlobalConfig.InstrumentationOptIn {
+ describeTags, _ := app.Describe()
+ for key, val := range describeTags {
+ if !nodeps.ArrayContainsString(ignoredProperties, key) {
+ nodeps.InstrumentationTags[key] = fmt.Sprintf("%v", val)
+ }
+ }
+ raven.SetTagsContext(nodeps.InstrumentationTags)
+ }
+}
+
+// SegmentUser does the enqueue of the Identify action, identifying the user
+// Here we just use the hashed hostid as the user id
+func SegmentUser(client analytics.Client, hashedID string) error {
+ err := client.Enqueue(analytics.Identify{
+ UserId: hashedID,
+ Traits: analytics.NewTraits().
+ Set("OS", runtime.GOOS).
+ Set("ddev-version", version.VERSION),
+ })
+
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// SegmentEvent provides the event and traits that go with it.
+func SegmentEvent(client analytics.Client, hashedID string, event string) error {
+
+ properties := analytics.NewProperties()
+
+ for key, val := range nodeps.InstrumentationTags {
+ if val != "" {
+ properties = properties.Set(key, val)
+ }
+ }
+
+ err := client.Enqueue(analytics.Track{
+ UserId: hashedID,
+ Event: event,
+ Properties: properties,
+ })
+
+ return err
+}
+
+// SendInstrumentationEvents does the actual send to sentry/segment
+func SendInstrumentationEvents(event string) {
+
+ if globalconfig.DdevGlobalConfig.InstrumentationOptIn && nodeps.IsInternetActive() {
+ _ = raven.CaptureMessageAndWait("Usage: ddev "+event, map[string]string{"severity-level": "info", "report-type": "usage"})
+
+ client := analytics.New(version.SegmentKey)
+
+ err := SegmentUser(client, hashedHostID)
+ if err != nil {
+ util.Warning("error sending hashedHostID to segment: %v", err)
+ }
+
+ err = SegmentEvent(client, hashedHostID, event)
+ if err != nil {
+ util.Warning("error sending event to segment: %v", err)
+ }
+ err = client.Close()
+ if err != nil {
+ util.Warning("segment analytics client.close() failed: %v", err)
+ }
+ }
+}
+
+func init() {
+ hashedHostID, _ = machineid.ProtectedID("ddev")
+}
diff --git a/pkg/ddevapp/ports.go b/pkg/ddevapp/ports.go
new file mode 100644
index 00000000000..1a57a860f22
--- /dev/null
+++ b/pkg/ddevapp/ports.go
@@ -0,0 +1,37 @@
+package ddevapp
+
+import (
+ "github.com/drud/ddev/pkg/util"
+ "strings"
+)
+
+// DBAPort defines the default phpmyadmin port used on the router.
+var dbaPort = "8036"
+
+// mailHogPort defines the default mailhog exposed by the router.
+var mailhogPort = "8025"
+
+// dbPort defines the default DB (MySQL) port.
+var dbPort = "3306"
+
+// webPort defines the internal web port
+var webPort = "80"
+
+var ports = map[string]string{
+ "mailhog": mailhogPort,
+ "dba": dbaPort,
+ "db": dbPort,
+ "web": webPort,
+}
+
+// GetPort returns the external router (as a string) for the given service. This can be used to find a given port for docker-compose manifests,
+// or for automated testing.
+func GetPort(service string) string {
+ service = strings.ToLower(service)
+ val, ok := ports[service]
+ if !ok {
+ util.Failed("Could not find port for service %s", service)
+ }
+
+ return val
+}
diff --git a/pkg/ddevapp/raven.go b/pkg/ddevapp/raven.go
deleted file mode 100644
index 65d401792cc..00000000000
--- a/pkg/ddevapp/raven.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package ddevapp
-
-import (
- "github.com/drud/ddev/pkg/globalconfig"
- "github.com/drud/ddev/pkg/nodeps"
- "github.com/drud/ddev/pkg/ravenutils"
- "github.com/drud/ddev/pkg/version"
- "github.com/getsentry/raven-go"
- "os"
- "runtime"
- "strconv"
- "strings"
-)
-
-// Set the basic always-used tags for Sentry/Raven
-func SetRavenBaseTags() {
- if globalconfig.DdevGlobalConfig.InstrumentationOptIn {
- dockerVersion, _ := version.GetDockerVersion()
- composeVersion, _ := version.GetDockerComposeVersion()
- isToolbox := nodeps.IsDockerToolbox()
-
- raven.SetRelease("ddev@" + version.COMMIT)
-
- tags := map[string]string{
- "OS": runtime.GOOS,
- "dockerVersion": dockerVersion,
- "dockerComposeVersion": composeVersion,
- "dockerToolbox": strconv.FormatBool(isToolbox),
- "ddevCommand": strings.Join(os.Args, " "),
- }
- ravenutils.AddRavenTags(tags)
- raven.SetTagsContext(tags)
- }
-}
-
-// Set app-specific tags for Sentry/Raven
-func (app *DdevApp) SetRavenTags() {
- if globalconfig.DdevGlobalConfig.InstrumentationOptIn {
- describeTags, _ := app.Describe()
- tags := map[string]string{}
- for key, val := range describeTags {
- var tagVal string
- if valString, ok := val.(string); ok {
- tagVal = valString
- } else if valAry, ok := val.([]string); ok {
- tagVal = strings.Join(valAry, " ")
- }
- if tagVal != "" {
- tags[key] = tagVal
- }
- }
- ravenutils.AddRavenTags(tags)
- raven.SetTagsContext(tags)
- }
-}
diff --git a/pkg/nodeps/instrumentation.go b/pkg/nodeps/instrumentation.go
new file mode 100644
index 00000000000..9942d1cd790
--- /dev/null
+++ b/pkg/nodeps/instrumentation.go
@@ -0,0 +1,3 @@
+package nodeps
+
+var InstrumentationTags = map[string]string{}
diff --git a/pkg/output/output_setup.go b/pkg/output/output_setup.go
index 2075313d86c..33f93bf7aa3 100644
--- a/pkg/output/output_setup.go
+++ b/pkg/output/output_setup.go
@@ -3,7 +3,6 @@ package output
import (
"github.com/drud/ddev/pkg/globalconfig"
"github.com/drud/ddev/pkg/nodeps"
- "github.com/drud/ddev/pkg/ravenutils"
"github.com/drud/ddev/pkg/version"
"github.com/evalphobia/logrus_sentry"
"os"
@@ -35,7 +34,7 @@ func LogSetUp() {
// Report errors and panics to Sentry
if version.SentryDSN != "" && !globalconfig.DdevNoSentry && nodeps.IsInternetActive() {
- hook, err := logrus_sentry.NewAsyncWithTagsSentryHook(version.SentryDSN, ravenutils.RavenTags, levels)
+ hook, err := logrus_sentry.NewAsyncWithTagsSentryHook(version.SentryDSN, nodeps.InstrumentationTags, levels)
if err == nil {
UserOut.Hooks.Add(hook)
}
diff --git a/pkg/ravenutils/raven.go b/pkg/ravenutils/raven.go
deleted file mode 100644
index 17c66ffb4d0..00000000000
--- a/pkg/ravenutils/raven.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package ravenutils
-
-// RavenTags provides exposed access to tags for Sentry
-var RavenTags = make(map[string]string)
-
-// AddRavenTags adds a set of tags to the exposed RavenTags so it can be used later
-// by logrus_sentry.NewAsyncWithTagsSentryHook
-func AddRavenTags(tags map[string]string) {
- for k, v := range tags {
- RavenTags[k] = v
- }
-}
diff --git a/pkg/version/version.go b/pkg/version/version.go
index bb7c64866f3..d83eb9fe72c 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -24,6 +24,10 @@ var DdevVersion = "v0.0.0-overridden-by-make" // Note that this is overridden by
// It is compiled in using link-time variables
var SentryDSN = ""
+// SegmentKey is the ddev-specific key for Segment service
+// Compiled with link-time variables
+var SegmentKey = ""
+
// DockerVersionConstraint is the current minimum version of docker required for ddev.
// See https://godoc.org/github.com/Masterminds/semver#hdr-Checking_Version_Constraints
// for examples defining version constraints.
diff --git a/vendor/github.com/denisbrodbeck/machineid/.gitignore b/vendor/github.com/denisbrodbeck/machineid/.gitignore
new file mode 100644
index 00000000000..9a40b5c6201
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/.gitignore
@@ -0,0 +1,29 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+.DS_Store
+.vscode
+result
+/machineid
diff --git a/vendor/github.com/denisbrodbeck/machineid/LICENSE.md b/vendor/github.com/denisbrodbeck/machineid/LICENSE.md
new file mode 100644
index 00000000000..31c4e6711af
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/LICENSE.md
@@ -0,0 +1,22 @@
+
+The MIT License (MIT)
+
+Copyright (c) 2017 Denis Brodbeck
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/denisbrodbeck/machineid/README.md b/vendor/github.com/denisbrodbeck/machineid/README.md
new file mode 100644
index 00000000000..b82802e02e5
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/README.md
@@ -0,0 +1,182 @@
+# machineid provides support for reading the unique machine id of most host OS's (without admin privileges)
+
+![Image of Gopher 47](logo.png)
+
+… because sometimes you just need to reliably identify your machines.
+
+[![GoDoc](https://godoc.org/github.com/denisbrodbeck/machineid?status.svg)](https://godoc.org/github.com/denisbrodbeck/machineid) [![Go Report Card](https://goreportcard.com/badge/github.com/denisbrodbeck/machineid)](https://goreportcard.com/report/github.com/denisbrodbeck/machineid)
+
+## Main Features
+
+* Cross-Platform (tested on Win7+, Debian 8+, Ubuntu 14.04+, OS X 10.6+, FreeBSD 11+)
+* No admin privileges required
+* Hardware independent (no usage of MAC, BIOS or CPU — those are too unreliable, especially in a VM environment)
+* IDs are unique[1](#unique-key-reliability) to the installed OS
+
+## Installation
+
+Get the library with
+
+```bash
+go get github.com/denisbrodbeck/machineid
+```
+
+You can also add the cli app directly to your `$GOPATH/bin` with
+
+```bash
+go get github.com/denisbrodbeck/machineid/cmd/machineid
+```
+
+## Usage
+
+```golang
+package main
+
+import (
+ "fmt"
+ "log"
+ "github.com/denisbrodbeck/machineid"
+)
+
+func main() {
+ id, err := machineid.ID()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println(id)
+}
+```
+
+Or even better, use securely hashed machine IDs:
+
+```golang
+package main
+
+import (
+ "fmt"
+ "log"
+ "github.com/denisbrodbeck/machineid"
+)
+
+func main() {
+ id, err := machineid.ProtectedID("myAppName")
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println(id)
+}
+```
+
+### Function: ID() (string, error)
+
+Returns original machine id as a `string`.
+
+### Function: ProtectedID(appID string) (string, error)
+
+Returns hashed version of the machine ID as a `string`. The hash is generated in a cryptographically secure way, using a fixed, application-specific key (calculates HMAC-SHA256 of the app ID, keyed by the machine ID).
+
+## What you get
+
+This package returns the OS native machine UUID/GUID, which the OS uses for internal needs.
+
+All machine IDs are usually generated during system installation and stay constant for all subsequent boots.
+
+The following sources are used:
+
+* **BSD** uses `/etc/hostid` and `smbios.system.uuid` as a fallback
+* **Linux** uses `/var/lib/dbus/machine-id` ([man](http://man7.org/linux/man-pages/man5/machine-id.5.html))
+* **OS X** uses `IOPlatformUUID`
+* **Windows** uses the `MachineGuid` from `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography`
+
+## Unique Key Reliability
+
+Do note, that `machine-id` and `MachineGuid` can be changed by root/admin, although that may not come without cost (broken system services and more).
+Most IDs won't be regenerated by the OS, when you clone/image/restore a particular OS installation. This is a well known issue with cloned windows installs (not using the official sysprep tools).
+
+**Linux** users can generate a new id with `dbus-uuidgen` and put the id into `/var/lib/dbus/machine-id` and `/etc/machine-id`.
+**Windows** users can use the `sysprep` [toolchain](https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/sysprep--generalize--a-windows-installation) to create images, which produce valid images ready for distribution. Such images produce a new unique machine ID on each deployment.
+
+## Security Considerations
+
+A machine ID uniquely identifies the host. Therefore it should be considered "confidential", and must not be exposed in untrusted environments. If you need a stable unique identifier for your app, do not use the machine ID directly.
+
+> A reliable solution is to hash the machine ID in a cryptographically secure way, using a fixed, application-specific key.
+
+That way the ID will be properly unique, and derived in a constant way from the machine ID but there will be no way to retrieve the original machine ID from the application-specific one.
+
+Do something along these lines:
+
+```golang
+package main
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "fmt"
+ "github.com/denisbrodbeck/machineid"
+)
+
+const appKey = "WowSuchNiceApp"
+
+func main() {
+ id, _ := machineid.ID()
+ fmt.Println(protect(appKey, id))
+ // Output: dbabdb7baa54845f9bec96e2e8a87be2d01794c66fdebac3df7edd857f3d9f97
+}
+
+func protect(appID, id string) string {
+ mac := hmac.New(sha256.New, []byte(id))
+ mac.Write([]byte(appID))
+ return fmt.Sprintf("%x", mac.Sum(nil))
+}
+```
+
+Or simply use the convenience API call:
+
+```golang
+hashedID, err := machineid.ProtectedID("myAppName")
+```
+
+## Snippets
+
+Don't want to download code, and just need a way to get the data by yourself?
+
+BSD:
+
+```bash
+cat /etc/hostid
+# or (might be empty)
+kenv -q smbios.system.uuid
+```
+
+Linux:
+
+```bash
+cat /var/lib/dbus/machine-id
+# or when not found (e.g. Fedora 20)
+cat /etc/machine-id
+```
+
+OS X:
+
+```bash
+ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID
+```
+
+Windows:
+
+```batch
+reg query HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography /v MachineGuid
+```
+or
+* Open Windows Registry via `regedit`
+* Navigate to `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography`
+* Take value of key `MachineGuid`
+
+## Credits
+
+The Go gopher was created by [Denis Brodbeck](https://github.com/denisbrodbeck) with [gopherize.me](https://gopherize.me/), based on original artwork from [Renee French](http://reneefrench.blogspot.com/).
+
+## License
+
+The MIT License (MIT) — [Denis Brodbeck](https://github.com/denisbrodbeck). Please have a look at the [LICENSE.md](LICENSE.md) for more details.
diff --git a/vendor/github.com/denisbrodbeck/machineid/helper.go b/vendor/github.com/denisbrodbeck/machineid/helper.go
new file mode 100644
index 00000000000..a16056ddc16
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/helper.go
@@ -0,0 +1,36 @@
+package machineid
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/hex"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+// run wraps `exec.Command` with easy access to stdout and stderr.
+func run(stdout, stderr io.Writer, cmd string, args ...string) error {
+ c := exec.Command(cmd, args...)
+ c.Stdin = os.Stdin
+ c.Stdout = stdout
+ c.Stderr = stderr
+ return c.Run()
+}
+
+// protect calculates HMAC-SHA256 of the application ID, keyed by the machine ID and returns a hex-encoded string.
+func protect(appID, id string) string {
+ mac := hmac.New(sha256.New, []byte(id))
+ mac.Write([]byte(appID))
+ return hex.EncodeToString(mac.Sum(nil))
+}
+
+func readFile(filename string) ([]byte, error) {
+ return ioutil.ReadFile(filename)
+}
+
+func trim(s string) string {
+ return strings.TrimSpace(strings.Trim(s, "\n"))
+}
diff --git a/vendor/github.com/denisbrodbeck/machineid/id.go b/vendor/github.com/denisbrodbeck/machineid/id.go
new file mode 100644
index 00000000000..3c7fb9413ea
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/id.go
@@ -0,0 +1,45 @@
+// Package machineid provides support for reading the unique machine id of most OSs (without admin privileges).
+//
+// https://github.com/denisbrodbeck/machineid
+//
+// https://godoc.org/github.com/denisbrodbeck/machineid/cmd/machineid
+//
+// This package is Cross-Platform (tested on Win7+, Debian 8+, Ubuntu 14.04+, OS X 10.6+, FreeBSD 11+)
+// and does not use any internal hardware IDs (no MAC, BIOS, or CPU).
+//
+// Returned machine IDs are generally stable for the OS installation
+// and usually stay the same after updates or hardware changes.
+//
+// This package allows sharing of machine IDs in a secure way by
+// calculating HMAC-SHA256 over a user provided app ID, which is keyed by the machine id.
+//
+// Caveat: Image-based environments have usually the same machine-id (perfect clone).
+// Linux users can generate a new id with `dbus-uuidgen` and put the id into
+// `/var/lib/dbus/machine-id` and `/etc/machine-id`.
+// Windows users can use the `sysprep` toolchain to create images, which produce valid images ready for distribution.
+package machineid // import "github.com/denisbrodbeck/machineid"
+
+import (
+ "fmt"
+)
+
+// ID returns the platform specific machine id of the current host OS.
+// Regard the returned id as "confidential" and consider using ProtectedID() instead.
+func ID() (string, error) {
+ id, err := machineID()
+ if err != nil {
+ return "", fmt.Errorf("machineid: %v", err)
+ }
+ return id, nil
+}
+
+// ProtectedID returns a hashed version of the machine ID in a cryptographically secure way,
+// using a fixed, application-specific key.
+// Internally, this function calculates HMAC-SHA256 of the application ID, keyed by the machine ID.
+func ProtectedID(appID string) (string, error) {
+ id, err := ID()
+ if err != nil {
+ return "", fmt.Errorf("machineid: %v", err)
+ }
+ return protect(appID, id), nil
+}
diff --git a/vendor/github.com/denisbrodbeck/machineid/id_bsd.go b/vendor/github.com/denisbrodbeck/machineid/id_bsd.go
new file mode 100644
index 00000000000..241b0f702e0
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/id_bsd.go
@@ -0,0 +1,42 @@
+// +build freebsd netbsd openbsd dragonfly solaris
+
+package machineid
+
+import (
+ "bytes"
+ "os"
+)
+
+const hostidPath = "/etc/hostid"
+
+// machineID returns the uuid specified at `/etc/hostid`.
+// If the returned value is empty, the uuid from a call to `kenv -q smbios.system.uuid` is returned.
+// If there is an error an empty string is returned.
+func machineID() (string, error) {
+ id, err := readHostid()
+ if err != nil {
+ // try fallback
+ id, err = readKenv()
+ }
+ if err != nil {
+ return "", err
+ }
+ return id, nil
+}
+
+func readHostid() (string, error) {
+ buf, err := readFile(hostidPath)
+ if err != nil {
+ return "", err
+ }
+ return trim(string(buf)), nil
+}
+
+func readKenv() (string, error) {
+ buf := &bytes.Buffer{}
+ err := run(buf, os.Stderr, "kenv", "-q", "smbios.system.uuid")
+ if err != nil {
+ return "", err
+ }
+ return trim(buf.String()), nil
+}
diff --git a/vendor/github.com/denisbrodbeck/machineid/id_darwin.go b/vendor/github.com/denisbrodbeck/machineid/id_darwin.go
new file mode 100644
index 00000000000..08510e4dd37
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/id_darwin.go
@@ -0,0 +1,37 @@
+// +build darwin
+
+package machineid
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "strings"
+)
+
+// machineID returns the uuid returned by `ioreg -rd1 -c IOPlatformExpertDevice`.
+// If there is an error running the commad an empty string is returned.
+func machineID() (string, error) {
+ buf := &bytes.Buffer{}
+ err := run(buf, os.Stderr, "ioreg", "-rd1", "-c", "IOPlatformExpertDevice")
+ if err != nil {
+ return "", err
+ }
+ id, err := extractID(buf.String())
+ if err != nil {
+ return "", err
+ }
+ return trim(id), nil
+}
+
+func extractID(lines string) (string, error) {
+ for _, line := range strings.Split(lines, "\n") {
+ if strings.Contains(line, "IOPlatformUUID") {
+ parts := strings.SplitAfter(line, `" = "`)
+ if len(parts) == 2 {
+ return strings.TrimRight(parts[1], `"`), nil
+ }
+ }
+ }
+ return "", fmt.Errorf("Failed to extract 'IOPlatformUUID' value from `ioreg` output.\n%s", lines)
+}
diff --git a/vendor/github.com/denisbrodbeck/machineid/id_linux.go b/vendor/github.com/denisbrodbeck/machineid/id_linux.go
new file mode 100644
index 00000000000..e68eb454773
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/id_linux.go
@@ -0,0 +1,27 @@
+// +build linux
+
+package machineid
+
+const (
+ // dbusPath is the default path for dbus machine id.
+ dbusPath = "/var/lib/dbus/machine-id"
+ // dbusPathEtc is the default path for dbus machine id located in /etc.
+ // Some systems (like Fedora 20) only know this path.
+ // Sometimes it's the other way round.
+ dbusPathEtc = "/etc/machine-id"
+)
+
+// machineID returns the uuid specified at `/var/lib/dbus/machine-id` or `/etc/machine-id`.
+// If there is an error reading the files an empty string is returned.
+// See https://unix.stackexchange.com/questions/144812/generate-consistent-machine-unique-id
+func machineID() (string, error) {
+ id, err := readFile(dbusPath)
+ if err != nil {
+ // try fallback path
+ id, err = readFile(dbusPathEtc)
+ }
+ if err != nil {
+ return "", err
+ }
+ return trim(string(id)), nil
+}
diff --git a/vendor/github.com/denisbrodbeck/machineid/id_windows.go b/vendor/github.com/denisbrodbeck/machineid/id_windows.go
new file mode 100644
index 00000000000..4b778ae4751
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/id_windows.go
@@ -0,0 +1,23 @@
+// +build windows
+
+package machineid
+
+import (
+ "golang.org/x/sys/windows/registry"
+)
+
+// machineID returns the key MachineGuid in registry `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography`.
+// If there is an error running the commad an empty string is returned.
+func machineID() (string, error) {
+ k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Cryptography`, registry.QUERY_VALUE|registry.WOW64_64KEY)
+ if err != nil {
+ return "", err
+ }
+ defer k.Close()
+
+ s, _, err := k.GetStringValue("MachineGuid")
+ if err != nil {
+ return "", err
+ }
+ return s, nil
+}
diff --git a/vendor/github.com/denisbrodbeck/machineid/logo.png b/vendor/github.com/denisbrodbeck/machineid/logo.png
new file mode 100644
index 00000000000..01536427401
Binary files /dev/null and b/vendor/github.com/denisbrodbeck/machineid/logo.png differ
diff --git a/vendor/github.com/denisbrodbeck/machineid/makefile b/vendor/github.com/denisbrodbeck/machineid/makefile
new file mode 100644
index 00000000000..e947a8132be
--- /dev/null
+++ b/vendor/github.com/denisbrodbeck/machineid/makefile
@@ -0,0 +1,12 @@
+.PHONY: build clean default test
+
+build: clean
+ @go build -o machineid ./cmd/machineid/main.go
+
+clean:
+ @rm -rf ./machineid
+
+test:
+ go test ./...
+
+default: build
diff --git a/vendor/github.com/segmentio/backo-go/.gitmodules b/vendor/github.com/segmentio/backo-go/.gitmodules
new file mode 100644
index 00000000000..36de9297737
--- /dev/null
+++ b/vendor/github.com/segmentio/backo-go/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "vendor/github.com/bmizerany/assert"]
+ path = vendor/github.com/bmizerany/assert
+ url = https://github.com/bmizerany/assert
diff --git a/vendor/github.com/segmentio/backo-go/README.md b/vendor/github.com/segmentio/backo-go/README.md
new file mode 100644
index 00000000000..1362becf11d
--- /dev/null
+++ b/vendor/github.com/segmentio/backo-go/README.md
@@ -0,0 +1,80 @@
+Backo [![GoDoc](http://godoc.org/github.com/segmentio/backo-go?status.png)](http://godoc.org/github.com/segmentio/backo-go)
+-----
+
+Exponential backoff for Go (Go port of segmentio/backo).
+
+
+Usage
+-----
+
+```go
+import "github.com/segmentio/backo-go"
+
+// Create a Backo instance.
+backo := backo.NewBacko(milliseconds(100), 2, 1, milliseconds(10*1000))
+// OR with defaults.
+backo := backo.DefaultBacko()
+
+// Use the ticker API.
+ticker := b.NewTicker()
+for {
+ timeout := time.After(5 * time.Minute)
+ select {
+ case <-ticker.C:
+ fmt.Println("ticked")
+ case <- timeout:
+ fmt.Println("timed out")
+ }
+}
+
+// Or simply work with backoff intervals directly.
+for i := 0; i < n; i++ {
+ // Sleep the current goroutine.
+ backo.Sleep(i)
+ // Retrieve the duration manually.
+ duration := backo.Duration(i)
+}
+```
+
+License
+-------
+
+```
+WWWWWW||WWWWWW
+ W W W||W W W
+ ||
+ ( OO )__________
+ / | \
+ /o o| MIT \
+ \___/||_||__||_|| *
+ || || || ||
+ _||_|| _||_||
+ (__|__|(__|__|
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Segment, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
+
+
+
+ [1]: http://github.com/segmentio/backo-java
+ [2]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.segment.backo&a=backo&v=LATEST
\ No newline at end of file
diff --git a/vendor/github.com/segmentio/backo-go/backo.go b/vendor/github.com/segmentio/backo-go/backo.go
new file mode 100644
index 00000000000..6f7b6d5e25d
--- /dev/null
+++ b/vendor/github.com/segmentio/backo-go/backo.go
@@ -0,0 +1,83 @@
+package backo
+
+import (
+ "math"
+ "math/rand"
+ "time"
+)
+
+type Backo struct {
+ base time.Duration
+ factor uint8
+ jitter float64
+ cap time.Duration
+}
+
+// Creates a backo instance with the given parameters
+func NewBacko(base time.Duration, factor uint8, jitter float64, cap time.Duration) *Backo {
+ return &Backo{base, factor, jitter, cap}
+}
+
+// Creates a backo instance with the following defaults:
+// base: 100 milliseconds
+// factor: 2
+// jitter: 0
+// cap: 10 seconds
+func DefaultBacko() *Backo {
+ return NewBacko(time.Millisecond*100, 2, 0, time.Second*10)
+}
+
+// Duration returns the backoff interval for the given attempt.
+func (backo *Backo) Duration(attempt int) time.Duration {
+ duration := float64(backo.base) * math.Pow(float64(backo.factor), float64(attempt))
+
+ if backo.jitter != 0 {
+ random := rand.Float64()
+ deviation := math.Floor(random * backo.jitter * duration)
+ if (int(math.Floor(random*10)) & 1) == 0 {
+ duration = duration - deviation
+ } else {
+ duration = duration + deviation
+ }
+ }
+
+ duration = math.Min(float64(duration), float64(backo.cap))
+ return time.Duration(duration)
+}
+
+// Sleep pauses the current goroutine for the backoff interval for the given attempt.
+func (backo *Backo) Sleep(attempt int) {
+ duration := backo.Duration(attempt)
+ time.Sleep(duration)
+}
+
+type Ticker struct {
+ done chan struct{}
+ C <-chan time.Time
+}
+
+func (b *Backo) NewTicker() *Ticker {
+ c := make(chan time.Time, 1)
+ ticker := &Ticker{
+ done: make(chan struct{}, 1),
+ C: c,
+ }
+
+ go func() {
+ for i := 0; ; i++ {
+ select {
+ case t := <-time.After(b.Duration(i)):
+ c <- t
+ case <-ticker.done:
+ close(c)
+ return
+ }
+ }
+ }()
+
+ return ticker
+}
+
+func (t *Ticker) Stop() {
+ t.done <- struct{}{}
+}
diff --git a/vendor/github.com/xtgo/uuid/AUTHORS b/vendor/github.com/xtgo/uuid/AUTHORS
new file mode 100644
index 00000000000..a6f04516077
--- /dev/null
+++ b/vendor/github.com/xtgo/uuid/AUTHORS
@@ -0,0 +1,5 @@
+# This source file refers to The gocql Authors for copyright purposes.
+
+Christoph Hack
+Jonathan Rudenberg
+Thorsten von Eicken
diff --git a/vendor/github.com/xtgo/uuid/LICENSE b/vendor/github.com/xtgo/uuid/LICENSE
new file mode 100644
index 00000000000..18d25f92349
--- /dev/null
+++ b/vendor/github.com/xtgo/uuid/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2012 The gocql Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/xtgo/uuid/uuid.go b/vendor/github.com/xtgo/uuid/uuid.go
new file mode 100644
index 00000000000..a0fd7a5a5c1
--- /dev/null
+++ b/vendor/github.com/xtgo/uuid/uuid.go
@@ -0,0 +1,204 @@
+// Copyright (c) 2012 The gocql Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package uuid can be used to generate and parse universally unique
+// identifiers, a standardized format in the form of a 128 bit number.
+//
+// http://tools.ietf.org/html/rfc4122
+package uuid
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+ "io"
+ "net"
+ "strconv"
+ "time"
+)
+
+type UUID [16]byte
+
+var hardwareAddr []byte
+
+const (
+ VariantNCSCompat = 0
+ VariantIETF = 2
+ VariantMicrosoft = 6
+ VariantFuture = 7
+)
+
+func init() {
+ if interfaces, err := net.Interfaces(); err == nil {
+ for _, i := range interfaces {
+ if i.Flags&net.FlagLoopback == 0 && len(i.HardwareAddr) > 0 {
+ hardwareAddr = i.HardwareAddr
+ break
+ }
+ }
+ }
+ if hardwareAddr == nil {
+ // If we failed to obtain the MAC address of the current computer,
+ // we will use a randomly generated 6 byte sequence instead and set
+ // the multicast bit as recommended in RFC 4122.
+ hardwareAddr = make([]byte, 6)
+ _, err := io.ReadFull(rand.Reader, hardwareAddr)
+ if err != nil {
+ panic(err)
+ }
+ hardwareAddr[0] = hardwareAddr[0] | 0x01
+ }
+}
+
+// Parse parses a 32 digit hexadecimal number (that might contain hyphens)
+// representing an UUID.
+func Parse(input string) (UUID, error) {
+ var u UUID
+ j := 0
+ for i := 0; i < len(input); i++ {
+ b := input[i]
+ switch {
+ default:
+ fallthrough
+ case j == 32:
+ goto err
+ case b == '-':
+ continue
+ case '0' <= b && b <= '9':
+ b -= '0'
+ case 'a' <= b && b <= 'f':
+ b -= 'a' - 10
+ case 'A' <= b && b <= 'F':
+ b -= 'A' - 10
+ }
+ u[j/2] |= b << byte(^j&1<<2)
+ j++
+ }
+ if j == 32 {
+ return u, nil
+ }
+err:
+ return UUID{}, errors.New("invalid UUID " + strconv.Quote(input))
+}
+
+// FromBytes converts a raw byte slice to an UUID. It will panic if the slice
+// isn't exactly 16 bytes long.
+func FromBytes(input []byte) UUID {
+ var u UUID
+ if len(input) != 16 {
+ panic("UUIDs must be exactly 16 bytes long")
+ }
+ copy(u[:], input)
+ return u
+}
+
+// NewRandom generates a totally random UUID (version 4) as described in
+// RFC 4122.
+func NewRandom() UUID {
+ var u UUID
+ io.ReadFull(rand.Reader, u[:])
+ u[6] &= 0x0F // clear version
+ u[6] |= 0x40 // set version to 4 (random uuid)
+ u[8] &= 0x3F // clear variant
+ u[8] |= 0x80 // set to IETF variant
+ return u
+}
+
+var timeBase = time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC).Unix()
+
+// NewTime generates a new time based UUID (version 1) as described in RFC
+// 4122. This UUID contains the MAC address of the node that generated the
+// UUID, a timestamp and a sequence number.
+func NewTime() UUID {
+ var u UUID
+
+ now := time.Now().In(time.UTC)
+ t := uint64(now.Unix()-timeBase)*10000000 + uint64(now.Nanosecond()/100)
+ u[0], u[1], u[2], u[3] = byte(t>>24), byte(t>>16), byte(t>>8), byte(t)
+ u[4], u[5] = byte(t>>40), byte(t>>32)
+ u[6], u[7] = byte(t>>56)&0x0F, byte(t>>48)
+
+ var clockSeq [2]byte
+ io.ReadFull(rand.Reader, clockSeq[:])
+ u[8] = clockSeq[1]
+ u[9] = clockSeq[0]
+
+ copy(u[10:], hardwareAddr)
+
+ u[6] |= 0x10 // set version to 1 (time based uuid)
+ u[8] &= 0x3F // clear variant
+ u[8] |= 0x80 // set to IETF variant
+
+ return u
+}
+
+// String returns the UUID in it's canonical form, a 32 digit hexadecimal
+// number in the form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
+func (u UUID) String() string {
+ buf := [36]byte{8: '-', 13: '-', 18: '-', 23: '-'}
+ hex.Encode(buf[0:], u[0:4])
+ hex.Encode(buf[9:], u[4:6])
+ hex.Encode(buf[14:], u[6:8])
+ hex.Encode(buf[19:], u[8:10])
+ hex.Encode(buf[24:], u[10:])
+ return string(buf[:])
+}
+
+// Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits
+// (16 bytes) long.
+func (u UUID) Bytes() []byte {
+ return u[:]
+}
+
+// Variant returns the variant of this UUID. This package will only generate
+// UUIDs in the IETF variant.
+func (u UUID) Variant() int {
+ x := u[8]
+ switch byte(0) {
+ case x & 0x80:
+ return VariantNCSCompat
+ case x & 0x40:
+ return VariantIETF
+ case x & 0x20:
+ return VariantMicrosoft
+ }
+ return VariantFuture
+}
+
+// Version extracts the version of this UUID variant. The RFC 4122 describes
+// five kinds of UUIDs.
+func (u UUID) Version() int {
+ return int(u[6] & 0xF0 >> 4)
+}
+
+// Node extracts the MAC address of the node who generated this UUID. It will
+// return nil if the UUID is not a time based UUID (version 1).
+func (u UUID) Node() []byte {
+ if u.Version() != 1 {
+ return nil
+ }
+ return u[10:]
+}
+
+// Timestamp extracts the timestamp information from a time based UUID
+// (version 1).
+func (u UUID) Timestamp() uint64 {
+ if u.Version() != 1 {
+ return 0
+ }
+ return uint64(u[0])<<24 + uint64(u[1])<<16 + uint64(u[2])<<8 +
+ uint64(u[3]) + uint64(u[4])<<40 + uint64(u[5])<<32 +
+ uint64(u[7])<<48 + uint64(u[6]&0x0F)<<56
+}
+
+// Time is like Timestamp, except that it returns a time.Time.
+func (u UUID) Time() time.Time {
+ t := u.Timestamp()
+ if t == 0 {
+ return time.Time{}
+ }
+ sec := t / 10000000
+ nsec := t - sec
+ return time.Unix(int64(sec)+timeBase, int64(nsec))
+}
diff --git a/vendor/golang.org/x/sys/windows/registry/key.go b/vendor/golang.org/x/sys/windows/registry/key.go
new file mode 100644
index 00000000000..c256483434f
--- /dev/null
+++ b/vendor/golang.org/x/sys/windows/registry/key.go
@@ -0,0 +1,198 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+// Package registry provides access to the Windows registry.
+//
+// Here is a simple example, opening a registry key and reading a string value from it.
+//
+// k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
+// if err != nil {
+// log.Fatal(err)
+// }
+// defer k.Close()
+//
+// s, _, err := k.GetStringValue("SystemRoot")
+// if err != nil {
+// log.Fatal(err)
+// }
+// fmt.Printf("Windows system root is %q\n", s)
+//
+package registry
+
+import (
+ "io"
+ "syscall"
+ "time"
+)
+
+const (
+ // Registry key security and access rights.
+ // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878.aspx
+ // for details.
+ ALL_ACCESS = 0xf003f
+ CREATE_LINK = 0x00020
+ CREATE_SUB_KEY = 0x00004
+ ENUMERATE_SUB_KEYS = 0x00008
+ EXECUTE = 0x20019
+ NOTIFY = 0x00010
+ QUERY_VALUE = 0x00001
+ READ = 0x20019
+ SET_VALUE = 0x00002
+ WOW64_32KEY = 0x00200
+ WOW64_64KEY = 0x00100
+ WRITE = 0x20006
+)
+
+// Key is a handle to an open Windows registry key.
+// Keys can be obtained by calling OpenKey; there are
+// also some predefined root keys such as CURRENT_USER.
+// Keys can be used directly in the Windows API.
+type Key syscall.Handle
+
+const (
+ // Windows defines some predefined root keys that are always open.
+ // An application can use these keys as entry points to the registry.
+ // Normally these keys are used in OpenKey to open new keys,
+ // but they can also be used anywhere a Key is required.
+ CLASSES_ROOT = Key(syscall.HKEY_CLASSES_ROOT)
+ CURRENT_USER = Key(syscall.HKEY_CURRENT_USER)
+ LOCAL_MACHINE = Key(syscall.HKEY_LOCAL_MACHINE)
+ USERS = Key(syscall.HKEY_USERS)
+ CURRENT_CONFIG = Key(syscall.HKEY_CURRENT_CONFIG)
+ PERFORMANCE_DATA = Key(syscall.HKEY_PERFORMANCE_DATA)
+)
+
+// Close closes open key k.
+func (k Key) Close() error {
+ return syscall.RegCloseKey(syscall.Handle(k))
+}
+
+// OpenKey opens a new key with path name relative to key k.
+// It accepts any open key, including CURRENT_USER and others,
+// and returns the new key and an error.
+// The access parameter specifies desired access rights to the
+// key to be opened.
+func OpenKey(k Key, path string, access uint32) (Key, error) {
+ p, err := syscall.UTF16PtrFromString(path)
+ if err != nil {
+ return 0, err
+ }
+ var subkey syscall.Handle
+ err = syscall.RegOpenKeyEx(syscall.Handle(k), p, 0, access, &subkey)
+ if err != nil {
+ return 0, err
+ }
+ return Key(subkey), nil
+}
+
+// OpenRemoteKey opens a predefined registry key on another
+// computer pcname. The key to be opened is specified by k, but
+// can only be one of LOCAL_MACHINE, PERFORMANCE_DATA or USERS.
+// If pcname is "", OpenRemoteKey returns local computer key.
+func OpenRemoteKey(pcname string, k Key) (Key, error) {
+ var err error
+ var p *uint16
+ if pcname != "" {
+ p, err = syscall.UTF16PtrFromString(`\\` + pcname)
+ if err != nil {
+ return 0, err
+ }
+ }
+ var remoteKey syscall.Handle
+ err = regConnectRegistry(p, syscall.Handle(k), &remoteKey)
+ if err != nil {
+ return 0, err
+ }
+ return Key(remoteKey), nil
+}
+
+// ReadSubKeyNames returns the names of subkeys of key k.
+// The parameter n controls the number of returned names,
+// analogous to the way os.File.Readdirnames works.
+func (k Key) ReadSubKeyNames(n int) ([]string, error) {
+ names := make([]string, 0)
+ // Registry key size limit is 255 bytes and described there:
+ // https://msdn.microsoft.com/library/windows/desktop/ms724872.aspx
+ buf := make([]uint16, 256) //plus extra room for terminating zero byte
+loopItems:
+ for i := uint32(0); ; i++ {
+ if n > 0 {
+ if len(names) == n {
+ return names, nil
+ }
+ }
+ l := uint32(len(buf))
+ for {
+ err := syscall.RegEnumKeyEx(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil)
+ if err == nil {
+ break
+ }
+ if err == syscall.ERROR_MORE_DATA {
+ // Double buffer size and try again.
+ l = uint32(2 * len(buf))
+ buf = make([]uint16, l)
+ continue
+ }
+ if err == _ERROR_NO_MORE_ITEMS {
+ break loopItems
+ }
+ return names, err
+ }
+ names = append(names, syscall.UTF16ToString(buf[:l]))
+ }
+ if n > len(names) {
+ return names, io.EOF
+ }
+ return names, nil
+}
+
+// CreateKey creates a key named path under open key k.
+// CreateKey returns the new key and a boolean flag that reports
+// whether the key already existed.
+// The access parameter specifies the access rights for the key
+// to be created.
+func CreateKey(k Key, path string, access uint32) (newk Key, openedExisting bool, err error) {
+ var h syscall.Handle
+ var d uint32
+ err = regCreateKeyEx(syscall.Handle(k), syscall.StringToUTF16Ptr(path),
+ 0, nil, _REG_OPTION_NON_VOLATILE, access, nil, &h, &d)
+ if err != nil {
+ return 0, false, err
+ }
+ return Key(h), d == _REG_OPENED_EXISTING_KEY, nil
+}
+
+// DeleteKey deletes the subkey path of key k and its values.
+func DeleteKey(k Key, path string) error {
+ return regDeleteKey(syscall.Handle(k), syscall.StringToUTF16Ptr(path))
+}
+
+// A KeyInfo describes the statistics of a key. It is returned by Stat.
+type KeyInfo struct {
+ SubKeyCount uint32
+ MaxSubKeyLen uint32 // size of the key's subkey with the longest name, in Unicode characters, not including the terminating zero byte
+ ValueCount uint32
+ MaxValueNameLen uint32 // size of the key's longest value name, in Unicode characters, not including the terminating zero byte
+ MaxValueLen uint32 // longest data component among the key's values, in bytes
+ lastWriteTime syscall.Filetime
+}
+
+// ModTime returns the key's last write time.
+func (ki *KeyInfo) ModTime() time.Time {
+ return time.Unix(0, ki.lastWriteTime.Nanoseconds())
+}
+
+// Stat retrieves information about the open key k.
+func (k Key) Stat() (*KeyInfo, error) {
+ var ki KeyInfo
+ err := syscall.RegQueryInfoKey(syscall.Handle(k), nil, nil, nil,
+ &ki.SubKeyCount, &ki.MaxSubKeyLen, nil, &ki.ValueCount,
+ &ki.MaxValueNameLen, &ki.MaxValueLen, nil, &ki.lastWriteTime)
+ if err != nil {
+ return nil, err
+ }
+ return &ki, nil
+}
diff --git a/vendor/golang.org/x/sys/windows/registry/mksyscall.go b/vendor/golang.org/x/sys/windows/registry/mksyscall.go
new file mode 100644
index 00000000000..0ac95ffe731
--- /dev/null
+++ b/vendor/golang.org/x/sys/windows/registry/mksyscall.go
@@ -0,0 +1,7 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package registry
+
+//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall.go
diff --git a/vendor/golang.org/x/sys/windows/registry/syscall.go b/vendor/golang.org/x/sys/windows/registry/syscall.go
new file mode 100644
index 00000000000..e66643cbaa6
--- /dev/null
+++ b/vendor/golang.org/x/sys/windows/registry/syscall.go
@@ -0,0 +1,32 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package registry
+
+import "syscall"
+
+const (
+ _REG_OPTION_NON_VOLATILE = 0
+
+ _REG_CREATED_NEW_KEY = 1
+ _REG_OPENED_EXISTING_KEY = 2
+
+ _ERROR_NO_MORE_ITEMS syscall.Errno = 259
+)
+
+func LoadRegLoadMUIString() error {
+ return procRegLoadMUIStringW.Find()
+}
+
+//sys regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW
+//sys regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW
+//sys regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW
+//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW
+//sys regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW
+//sys regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) = advapi32.RegLoadMUIStringW
+//sys regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) = advapi32.RegConnectRegistryW
+
+//sys expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW
diff --git a/vendor/golang.org/x/sys/windows/registry/value.go b/vendor/golang.org/x/sys/windows/registry/value.go
new file mode 100644
index 00000000000..71d4e15bab1
--- /dev/null
+++ b/vendor/golang.org/x/sys/windows/registry/value.go
@@ -0,0 +1,384 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build windows
+
+package registry
+
+import (
+ "errors"
+ "io"
+ "syscall"
+ "unicode/utf16"
+ "unsafe"
+)
+
+const (
+ // Registry value types.
+ NONE = 0
+ SZ = 1
+ EXPAND_SZ = 2
+ BINARY = 3
+ DWORD = 4
+ DWORD_BIG_ENDIAN = 5
+ LINK = 6
+ MULTI_SZ = 7
+ RESOURCE_LIST = 8
+ FULL_RESOURCE_DESCRIPTOR = 9
+ RESOURCE_REQUIREMENTS_LIST = 10
+ QWORD = 11
+)
+
+var (
+ // ErrShortBuffer is returned when the buffer was too short for the operation.
+ ErrShortBuffer = syscall.ERROR_MORE_DATA
+
+ // ErrNotExist is returned when a registry key or value does not exist.
+ ErrNotExist = syscall.ERROR_FILE_NOT_FOUND
+
+ // ErrUnexpectedType is returned by Get*Value when the value's type was unexpected.
+ ErrUnexpectedType = errors.New("unexpected key value type")
+)
+
+// GetValue retrieves the type and data for the specified value associated
+// with an open key k. It fills up buffer buf and returns the retrieved
+// byte count n. If buf is too small to fit the stored value it returns
+// ErrShortBuffer error along with the required buffer size n.
+// If no buffer is provided, it returns true and actual buffer size n.
+// If no buffer is provided, GetValue returns the value's type only.
+// If the value does not exist, the error returned is ErrNotExist.
+//
+// GetValue is a low level function. If value's type is known, use the appropriate
+// Get*Value function instead.
+func (k Key) GetValue(name string, buf []byte) (n int, valtype uint32, err error) {
+ pname, err := syscall.UTF16PtrFromString(name)
+ if err != nil {
+ return 0, 0, err
+ }
+ var pbuf *byte
+ if len(buf) > 0 {
+ pbuf = (*byte)(unsafe.Pointer(&buf[0]))
+ }
+ l := uint32(len(buf))
+ err = syscall.RegQueryValueEx(syscall.Handle(k), pname, nil, &valtype, pbuf, &l)
+ if err != nil {
+ return int(l), valtype, err
+ }
+ return int(l), valtype, nil
+}
+
+func (k Key) getValue(name string, buf []byte) (date []byte, valtype uint32, err error) {
+ p, err := syscall.UTF16PtrFromString(name)
+ if err != nil {
+ return nil, 0, err
+ }
+ var t uint32
+ n := uint32(len(buf))
+ for {
+ err = syscall.RegQueryValueEx(syscall.Handle(k), p, nil, &t, (*byte)(unsafe.Pointer(&buf[0])), &n)
+ if err == nil {
+ return buf[:n], t, nil
+ }
+ if err != syscall.ERROR_MORE_DATA {
+ return nil, 0, err
+ }
+ if n <= uint32(len(buf)) {
+ return nil, 0, err
+ }
+ buf = make([]byte, n)
+ }
+}
+
+// GetStringValue retrieves the string value for the specified
+// value name associated with an open key k. It also returns the value's type.
+// If value does not exist, GetStringValue returns ErrNotExist.
+// If value is not SZ or EXPAND_SZ, it will return the correct value
+// type and ErrUnexpectedType.
+func (k Key) GetStringValue(name string) (val string, valtype uint32, err error) {
+ data, typ, err2 := k.getValue(name, make([]byte, 64))
+ if err2 != nil {
+ return "", typ, err2
+ }
+ switch typ {
+ case SZ, EXPAND_SZ:
+ default:
+ return "", typ, ErrUnexpectedType
+ }
+ if len(data) == 0 {
+ return "", typ, nil
+ }
+ u := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:]
+ return syscall.UTF16ToString(u), typ, nil
+}
+
+// GetMUIStringValue retrieves the localized string value for
+// the specified value name associated with an open key k.
+// If the value name doesn't exist or the localized string value
+// can't be resolved, GetMUIStringValue returns ErrNotExist.
+// GetMUIStringValue panics if the system doesn't support
+// regLoadMUIString; use LoadRegLoadMUIString to check if
+// regLoadMUIString is supported before calling this function.
+func (k Key) GetMUIStringValue(name string) (string, error) {
+ pname, err := syscall.UTF16PtrFromString(name)
+ if err != nil {
+ return "", err
+ }
+
+ buf := make([]uint16, 1024)
+ var buflen uint32
+ var pdir *uint16
+
+ err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
+ if err == syscall.ERROR_FILE_NOT_FOUND { // Try fallback path
+
+ // Try to resolve the string value using the system directory as
+ // a DLL search path; this assumes the string value is of the form
+ // @[path]\dllname,-strID but with no path given, e.g. @tzres.dll,-320.
+
+ // This approach works with tzres.dll but may have to be revised
+ // in the future to allow callers to provide custom search paths.
+
+ var s string
+ s, err = ExpandString("%SystemRoot%\\system32\\")
+ if err != nil {
+ return "", err
+ }
+ pdir, err = syscall.UTF16PtrFromString(s)
+ if err != nil {
+ return "", err
+ }
+
+ err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
+ }
+
+ for err == syscall.ERROR_MORE_DATA { // Grow buffer if needed
+ if buflen <= uint32(len(buf)) {
+ break // Buffer not growing, assume race; break
+ }
+ buf = make([]uint16, buflen)
+ err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir)
+ }
+
+ if err != nil {
+ return "", err
+ }
+
+ return syscall.UTF16ToString(buf), nil
+}
+
+// ExpandString expands environment-variable strings and replaces
+// them with the values defined for the current user.
+// Use ExpandString to expand EXPAND_SZ strings.
+func ExpandString(value string) (string, error) {
+ if value == "" {
+ return "", nil
+ }
+ p, err := syscall.UTF16PtrFromString(value)
+ if err != nil {
+ return "", err
+ }
+ r := make([]uint16, 100)
+ for {
+ n, err := expandEnvironmentStrings(p, &r[0], uint32(len(r)))
+ if err != nil {
+ return "", err
+ }
+ if n <= uint32(len(r)) {
+ u := (*[1 << 29]uint16)(unsafe.Pointer(&r[0]))[:]
+ return syscall.UTF16ToString(u), nil
+ }
+ r = make([]uint16, n)
+ }
+}
+
+// GetStringsValue retrieves the []string value for the specified
+// value name associated with an open key k. It also returns the value's type.
+// If value does not exist, GetStringsValue returns ErrNotExist.
+// If value is not MULTI_SZ, it will return the correct value
+// type and ErrUnexpectedType.
+func (k Key) GetStringsValue(name string) (val []string, valtype uint32, err error) {
+ data, typ, err2 := k.getValue(name, make([]byte, 64))
+ if err2 != nil {
+ return nil, typ, err2
+ }
+ if typ != MULTI_SZ {
+ return nil, typ, ErrUnexpectedType
+ }
+ if len(data) == 0 {
+ return nil, typ, nil
+ }
+ p := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:len(data)/2]
+ if len(p) == 0 {
+ return nil, typ, nil
+ }
+ if p[len(p)-1] == 0 {
+ p = p[:len(p)-1] // remove terminating null
+ }
+ val = make([]string, 0, 5)
+ from := 0
+ for i, c := range p {
+ if c == 0 {
+ val = append(val, string(utf16.Decode(p[from:i])))
+ from = i + 1
+ }
+ }
+ return val, typ, nil
+}
+
+// GetIntegerValue retrieves the integer value for the specified
+// value name associated with an open key k. It also returns the value's type.
+// If value does not exist, GetIntegerValue returns ErrNotExist.
+// If value is not DWORD or QWORD, it will return the correct value
+// type and ErrUnexpectedType.
+func (k Key) GetIntegerValue(name string) (val uint64, valtype uint32, err error) {
+ data, typ, err2 := k.getValue(name, make([]byte, 8))
+ if err2 != nil {
+ return 0, typ, err2
+ }
+ switch typ {
+ case DWORD:
+ if len(data) != 4 {
+ return 0, typ, errors.New("DWORD value is not 4 bytes long")
+ }
+ return uint64(*(*uint32)(unsafe.Pointer(&data[0]))), DWORD, nil
+ case QWORD:
+ if len(data) != 8 {
+ return 0, typ, errors.New("QWORD value is not 8 bytes long")
+ }
+ return uint64(*(*uint64)(unsafe.Pointer(&data[0]))), QWORD, nil
+ default:
+ return 0, typ, ErrUnexpectedType
+ }
+}
+
+// GetBinaryValue retrieves the binary value for the specified
+// value name associated with an open key k. It also returns the value's type.
+// If value does not exist, GetBinaryValue returns ErrNotExist.
+// If value is not BINARY, it will return the correct value
+// type and ErrUnexpectedType.
+func (k Key) GetBinaryValue(name string) (val []byte, valtype uint32, err error) {
+ data, typ, err2 := k.getValue(name, make([]byte, 64))
+ if err2 != nil {
+ return nil, typ, err2
+ }
+ if typ != BINARY {
+ return nil, typ, ErrUnexpectedType
+ }
+ return data, typ, nil
+}
+
+func (k Key) setValue(name string, valtype uint32, data []byte) error {
+ p, err := syscall.UTF16PtrFromString(name)
+ if err != nil {
+ return err
+ }
+ if len(data) == 0 {
+ return regSetValueEx(syscall.Handle(k), p, 0, valtype, nil, 0)
+ }
+ return regSetValueEx(syscall.Handle(k), p, 0, valtype, &data[0], uint32(len(data)))
+}
+
+// SetDWordValue sets the data and type of a name value
+// under key k to value and DWORD.
+func (k Key) SetDWordValue(name string, value uint32) error {
+ return k.setValue(name, DWORD, (*[4]byte)(unsafe.Pointer(&value))[:])
+}
+
+// SetQWordValue sets the data and type of a name value
+// under key k to value and QWORD.
+func (k Key) SetQWordValue(name string, value uint64) error {
+ return k.setValue(name, QWORD, (*[8]byte)(unsafe.Pointer(&value))[:])
+}
+
+func (k Key) setStringValue(name string, valtype uint32, value string) error {
+ v, err := syscall.UTF16FromString(value)
+ if err != nil {
+ return err
+ }
+ buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2]
+ return k.setValue(name, valtype, buf)
+}
+
+// SetStringValue sets the data and type of a name value
+// under key k to value and SZ. The value must not contain a zero byte.
+func (k Key) SetStringValue(name, value string) error {
+ return k.setStringValue(name, SZ, value)
+}
+
+// SetExpandStringValue sets the data and type of a name value
+// under key k to value and EXPAND_SZ. The value must not contain a zero byte.
+func (k Key) SetExpandStringValue(name, value string) error {
+ return k.setStringValue(name, EXPAND_SZ, value)
+}
+
+// SetStringsValue sets the data and type of a name value
+// under key k to value and MULTI_SZ. The value strings
+// must not contain a zero byte.
+func (k Key) SetStringsValue(name string, value []string) error {
+ ss := ""
+ for _, s := range value {
+ for i := 0; i < len(s); i++ {
+ if s[i] == 0 {
+ return errors.New("string cannot have 0 inside")
+ }
+ }
+ ss += s + "\x00"
+ }
+ v := utf16.Encode([]rune(ss + "\x00"))
+ buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2]
+ return k.setValue(name, MULTI_SZ, buf)
+}
+
+// SetBinaryValue sets the data and type of a name value
+// under key k to value and BINARY.
+func (k Key) SetBinaryValue(name string, value []byte) error {
+ return k.setValue(name, BINARY, value)
+}
+
+// DeleteValue removes a named value from the key k.
+func (k Key) DeleteValue(name string) error {
+ return regDeleteValue(syscall.Handle(k), syscall.StringToUTF16Ptr(name))
+}
+
+// ReadValueNames returns the value names of key k.
+// The parameter n controls the number of returned names,
+// analogous to the way os.File.Readdirnames works.
+func (k Key) ReadValueNames(n int) ([]string, error) {
+ ki, err := k.Stat()
+ if err != nil {
+ return nil, err
+ }
+ names := make([]string, 0, ki.ValueCount)
+ buf := make([]uint16, ki.MaxValueNameLen+1) // extra room for terminating null character
+loopItems:
+ for i := uint32(0); ; i++ {
+ if n > 0 {
+ if len(names) == n {
+ return names, nil
+ }
+ }
+ l := uint32(len(buf))
+ for {
+ err := regEnumValue(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil)
+ if err == nil {
+ break
+ }
+ if err == syscall.ERROR_MORE_DATA {
+ // Double buffer size and try again.
+ l = uint32(2 * len(buf))
+ buf = make([]uint16, l)
+ continue
+ }
+ if err == _ERROR_NO_MORE_ITEMS {
+ break loopItems
+ }
+ return names, err
+ }
+ names = append(names, syscall.UTF16ToString(buf[:l]))
+ }
+ if n > len(names) {
+ return names, io.EOF
+ }
+ return names, nil
+}
diff --git a/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go
new file mode 100644
index 00000000000..3778075da0f
--- /dev/null
+++ b/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go
@@ -0,0 +1,120 @@
+// Code generated by 'go generate'; DO NOT EDIT.
+
+package registry
+
+import (
+ "syscall"
+ "unsafe"
+
+ "golang.org/x/sys/windows"
+)
+
+var _ unsafe.Pointer
+
+// Do the interface allocations only once for common
+// Errno values.
+const (
+ errnoERROR_IO_PENDING = 997
+)
+
+var (
+ errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
+)
+
+// errnoErr returns common boxed Errno values, to prevent
+// allocations at runtime.
+func errnoErr(e syscall.Errno) error {
+ switch e {
+ case 0:
+ return nil
+ case errnoERROR_IO_PENDING:
+ return errERROR_IO_PENDING
+ }
+ // TODO: add more here, after collecting data on the common
+ // error values see on Windows. (perhaps when running
+ // all.bat?)
+ return e
+}
+
+var (
+ modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
+ modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
+
+ procRegCreateKeyExW = modadvapi32.NewProc("RegCreateKeyExW")
+ procRegDeleteKeyW = modadvapi32.NewProc("RegDeleteKeyW")
+ procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW")
+ procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW")
+ procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW")
+ procRegLoadMUIStringW = modadvapi32.NewProc("RegLoadMUIStringW")
+ procRegConnectRegistryW = modadvapi32.NewProc("RegConnectRegistryW")
+ procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
+)
+
+func regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) {
+ r0, _, _ := syscall.Syscall9(procRegCreateKeyExW.Addr(), 9, uintptr(key), uintptr(unsafe.Pointer(subkey)), uintptr(reserved), uintptr(unsafe.Pointer(class)), uintptr(options), uintptr(desired), uintptr(unsafe.Pointer(sa)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(disposition)))
+ if r0 != 0 {
+ regerrno = syscall.Errno(r0)
+ }
+ return
+}
+
+func regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) {
+ r0, _, _ := syscall.Syscall(procRegDeleteKeyW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(subkey)), 0)
+ if r0 != 0 {
+ regerrno = syscall.Errno(r0)
+ }
+ return
+}
+
+func regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) {
+ r0, _, _ := syscall.Syscall6(procRegSetValueExW.Addr(), 6, uintptr(key), uintptr(unsafe.Pointer(valueName)), uintptr(reserved), uintptr(vtype), uintptr(unsafe.Pointer(buf)), uintptr(bufsize))
+ if r0 != 0 {
+ regerrno = syscall.Errno(r0)
+ }
+ return
+}
+
+func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) {
+ r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valtype)), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(buflen)), 0)
+ if r0 != 0 {
+ regerrno = syscall.Errno(r0)
+ }
+ return
+}
+
+func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) {
+ r0, _, _ := syscall.Syscall(procRegDeleteValueW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(name)), 0)
+ if r0 != 0 {
+ regerrno = syscall.Errno(r0)
+ }
+ return
+}
+
+func regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) {
+ r0, _, _ := syscall.Syscall9(procRegLoadMUIStringW.Addr(), 7, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(unsafe.Pointer(buflenCopied)), uintptr(flags), uintptr(unsafe.Pointer(dir)), 0, 0)
+ if r0 != 0 {
+ regerrno = syscall.Errno(r0)
+ }
+ return
+}
+
+func regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) {
+ r0, _, _ := syscall.Syscall(procRegConnectRegistryW.Addr(), 3, uintptr(unsafe.Pointer(machinename)), uintptr(key), uintptr(unsafe.Pointer(result)))
+ if r0 != 0 {
+ regerrno = syscall.Errno(r0)
+ }
+ return
+}
+
+func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) {
+ r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size))
+ n = uint32(r0)
+ if n == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/.gitignore b/vendor/gopkg.in/segmentio/analytics-go.v3/.gitignore
new file mode 100644
index 00000000000..942678bd910
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/.gitignore
@@ -0,0 +1,32 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+# Emacs
+*~
+\#*
+.\#*
+
+# Artifacts
+tmp/*
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/.gitmodules b/vendor/gopkg.in/segmentio/analytics-go.v3/.gitmodules
new file mode 100644
index 00000000000..b2150b34275
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "vendor/github.com/segmentio/backo-go"]
+ path = vendor/github.com/segmentio/backo-go
+ url = https://github.com/segmentio/backo-go
+[submodule "vendor/github.com/xtgo/uuid"]
+ path = vendor/github.com/xtgo/uuid
+ url = https://github.com/xtgo/uuid
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/History.md b/vendor/gopkg.in/segmentio/analytics-go.v3/History.md
new file mode 100644
index 00000000000..42bc0f654bf
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/History.md
@@ -0,0 +1,78 @@
+v3.0.1 / 2018-10-02
+===================
+
+* Migrate from Circle V1 format to Circle V2
+* Adds CLI for sending segment events
+* Vendor packages back-go and uuid instead of using gitsubmodules
+
+
+v3.0.0 / 2016-06-02
+===================
+
+ * 3.0 is a significant rewrite with multiple breaking changes.
+ * [Quickstart](https://segment.com/docs/sources/server/go/quickstart/).
+ * [Documentation](https://segment.com/docs/sources/server/go/).
+ * [GoDocs](https://godoc.org/gopkg.in/segmentio/analytics-go.v3).
+ * [What's New in v3](https://segment.com/docs/sources/server/go/#what-s-new-in-v3).
+
+
+v2.1.0 / 2015-12-28
+===================
+
+ * Add ability to set custom timestamps for messages.
+ * Add ability to set a custom `net/http` client.
+ * Add ability to set a custom logger.
+ * Fix edge case when client would try to upload no messages.
+ * Properly upload in-flight messages when client is asked to shutdown.
+ * Add ability to set `.integrations` field on messages.
+ * Fix resource leak with interval ticker after shutdown.
+ * Add retries and back-off when uploading messages.
+ * Add ability to set custom flush interval.
+
+v2.0.0 / 2015-02-03
+===================
+
+ * rewrite with breaking API changes
+
+v1.2.0 / 2014-09-03
+==================
+
+ * add public .Flush() method
+ * rename .Stop() to .Close()
+
+v1.1.0 / 2014-09-02
+==================
+
+ * add client.Stop() to flash/wait. Closes #7
+
+v1.0.0 / 2014-08-26
+==================
+
+ * fix response close
+ * change comments to be more go-like
+ * change uuid libraries
+
+0.1.2 / 2014-06-11
+==================
+
+ * add runnable example
+ * fix: close body
+
+0.1.1 / 2014-05-31
+==================
+
+ * refactor locking
+
+0.1.0 / 2014-05-22
+==================
+
+ * replace Debug option with debug package
+
+0.0.2 / 2014-05-20
+==================
+
+ * add .Start()
+ * add mutexes
+ * rename BufferSize to FlushAt and FlushInterval to FlushAfter
+ * lower FlushInterval to 5 seconds
+ * lower BufferSize to 20 to match other clients
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/License.md b/vendor/gopkg.in/segmentio/analytics-go.v3/License.md
new file mode 100644
index 00000000000..f452c5d0fd0
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/License.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Segment, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/Makefile b/vendor/gopkg.in/segmentio/analytics-go.v3/Makefile
new file mode 100644
index 00000000000..c9debaaf438
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/Makefile
@@ -0,0 +1,31 @@
+ifndef CIRCLE_ARTIFACTS
+CIRCLE_ARTIFACTS=tmp
+endif
+
+bootstrap:
+ .buildscript/bootstrap.sh
+
+dependencies:
+ @go get -v -t ./...
+
+vet:
+ @go vet ./...
+
+test: vet
+ @mkdir -p ${CIRCLE_ARTIFACTS}
+ @go test -race -coverprofile=${CIRCLE_ARTIFACTS}/cover.out .
+ @go tool cover -func ${CIRCLE_ARTIFACTS}/cover.out -o ${CIRCLE_ARTIFACTS}/cover.txt
+ @go tool cover -html ${CIRCLE_ARTIFACTS}/cover.out -o ${CIRCLE_ARTIFACTS}/cover.html
+
+build: test
+ @go build ./...
+
+e2e:
+ @if [ "$(RUN_E2E_TESTS)" != "true" ]; then \
+ echo "Skipping end to end tests."; else \
+ go get github.com/segmentio/library-e2e-tester/cmd/tester; \
+ tester -segment-write-key=$(SEGMENT_WRITE_KEY) -webhook-auth-username=$(WEBHOOK_AUTH_USERNAME) -webhook-bucket=$(WEBHOOK_BUCKET) -path='cli'; fi
+
+ci: dependencies test e2e
+
+.PHONY: bootstrap dependencies vet test e2e ci
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/Readme.md b/vendor/gopkg.in/segmentio/analytics-go.v3/Readme.md
new file mode 100644
index 00000000000..7d369c16e85
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/Readme.md
@@ -0,0 +1,55 @@
+# analytics-go [![Circle CI](https://circleci.com/gh/segmentio/analytics-go/tree/master.svg?style=shield)](https://circleci.com/gh/segmentio/analytics-go/tree/master) [![go-doc](https://godoc.org/github.com/segmentio/analytics-go?status.svg)](https://godoc.org/github.com/segmentio/analytics-go)
+
+Segment analytics client for Go.
+
+## Installation
+
+The package can be simply installed via go get, we recommend that you use a
+package version management system like the Go vendor directory or a tool like
+Godep to avoid issues related to API breaking changes introduced between major
+versions of the library.
+
+To install it in the GOPATH:
+```
+go get https://github.com/segmentio/analytics-go
+```
+
+## Documentation
+
+The links bellow should provide all the documentation needed to make the best
+use of the library and the Segment API:
+
+- [Documentation](https://segment.com/docs/libraries/go/)
+- [godoc](https://godoc.org/gopkg.in/segmentio/analytics-go.v3)
+- [API](https://segment.com/docs/libraries/http/)
+- [Specs](https://segment.com/docs/spec/)
+
+## Usage
+
+```go
+package main
+
+import (
+ "os"
+
+ "github.com/segmentio/analytics-go"
+)
+
+func main() {
+ // Instantiates a client to use send messages to the segment API.
+ client := analytics.New(os.Getenv("SEGMENT_WRITE_KEY"))
+
+ // Enqueues a track event that will be sent asynchronously.
+ client.Enqueue(analytics.Track{
+ UserId: "test-user",
+ Event: "test-snippet",
+ })
+
+ // Flushes any queued messages and closes the client.
+ client.Close()
+}
+```
+
+## License
+
+The library is released under the [MIT license](License.md).
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/alias.go b/vendor/gopkg.in/segmentio/analytics-go.v3/alias.go
new file mode 100644
index 00000000000..0084d20b4da
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/alias.go
@@ -0,0 +1,38 @@
+package analytics
+
+import "time"
+
+// This type represents object sent in a alias call as described in
+// https://segment.com/docs/libraries/http/#alias
+type Alias struct {
+ // This field is exported for serialization purposes and shouldn't be set by
+ // the application, its value is always overwritten by the library.
+ Type string `json:"type,omitempty"`
+
+ MessageId string `json:"messageId,omitempty"`
+ PreviousId string `json:"previousId"`
+ UserId string `json:"userId"`
+ Timestamp time.Time `json:"timestamp,omitempty"`
+ Context *Context `json:"context,omitempty"`
+ Integrations Integrations `json:"integrations,omitempty"`
+}
+
+func (msg Alias) validate() error {
+ if len(msg.UserId) == 0 {
+ return FieldError{
+ Type: "analytics.Alias",
+ Name: "UserId",
+ Value: msg.UserId,
+ }
+ }
+
+ if len(msg.PreviousId) == 0 {
+ return FieldError{
+ Type: "analytics.Alias",
+ Name: "PreviousId",
+ Value: msg.PreviousId,
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/analytics.go b/vendor/gopkg.in/segmentio/analytics-go.v3/analytics.go
new file mode 100644
index 00000000000..793df10b889
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/analytics.go
@@ -0,0 +1,388 @@
+package analytics
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "sync"
+
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "time"
+)
+
+// Version of the client.
+const Version = "3.0.0"
+
+// This interface is the main API exposed by the analytics package.
+// Values that satsify this interface are returned by the client constructors
+// provided by the package and provide a way to send messages via the HTTP API.
+type Client interface {
+ io.Closer
+
+ // Queues a message to be sent by the client when the conditions for a batch
+ // upload are met.
+ // This is the main method you'll be using, a typical flow would look like
+ // this:
+ //
+ // client := analytics.New(writeKey)
+ // ...
+ // client.Enqueue(analytics.Track{ ... })
+ // ...
+ // client.Close()
+ //
+ // The method returns an error if the message queue not be queued, which
+ // happens if the client was already closed at the time the method was
+ // called or if the message was malformed.
+ Enqueue(Message) error
+}
+
+type client struct {
+ Config
+ key string
+
+ // This channel is where the `Enqueue` method writes messages so they can be
+ // picked up and pushed by the backend goroutine taking care of applying the
+ // batching rules.
+ msgs chan Message
+
+ // These two channels are used to synchronize the client shutting down when
+ // `Close` is called.
+ // The first channel is closed to signal the backend goroutine that it has
+ // to stop, then the second one is closed by the backend goroutine to signal
+ // that it has finished flushing all queued messages.
+ quit chan struct{}
+ shutdown chan struct{}
+
+ // This HTTP client is used to send requests to the backend, it uses the
+ // HTTP transport provided in the configuration.
+ http http.Client
+}
+
+// Instantiate a new client that uses the write key passed as first argument to
+// send messages to the backend.
+// The client is created with the default configuration.
+func New(writeKey string) Client {
+ // Here we can ignore the error because the default config is always valid.
+ c, _ := NewWithConfig(writeKey, Config{})
+ return c
+}
+
+// Instantiate a new client that uses the write key and configuration passed as
+// arguments to send messages to the backend.
+// The function will return an error if the configuration contained impossible
+// values (like a negative flush interval for example).
+// When the function returns an error the returned client will always be nil.
+func NewWithConfig(writeKey string, config Config) (cli Client, err error) {
+ if err = config.validate(); err != nil {
+ return
+ }
+
+ c := &client{
+ Config: makeConfig(config),
+ key: writeKey,
+ msgs: make(chan Message, 100),
+ quit: make(chan struct{}),
+ shutdown: make(chan struct{}),
+ http: makeHttpClient(config.Transport),
+ }
+
+ go c.loop()
+
+ cli = c
+ return
+}
+
+func makeHttpClient(transport http.RoundTripper) http.Client {
+ httpClient := http.Client{
+ Transport: transport,
+ }
+ if supportsTimeout(transport) {
+ httpClient.Timeout = 10 * time.Second
+ }
+ return httpClient
+}
+
+func (c *client) Enqueue(msg Message) (err error) {
+ if err = msg.validate(); err != nil {
+ return
+ }
+
+ var id = c.uid()
+ var ts = c.now()
+
+ switch m := msg.(type) {
+ case Alias:
+ m.Type = "alias"
+ m.MessageId = makeMessageId(m.MessageId, id)
+ m.Timestamp = makeTimestamp(m.Timestamp, ts)
+ msg = m
+
+ case Group:
+ m.Type = "group"
+ m.MessageId = makeMessageId(m.MessageId, id)
+ m.Timestamp = makeTimestamp(m.Timestamp, ts)
+ msg = m
+
+ case Identify:
+ m.Type = "identify"
+ m.MessageId = makeMessageId(m.MessageId, id)
+ m.Timestamp = makeTimestamp(m.Timestamp, ts)
+ msg = m
+
+ case Page:
+ m.Type = "page"
+ m.MessageId = makeMessageId(m.MessageId, id)
+ m.Timestamp = makeTimestamp(m.Timestamp, ts)
+ msg = m
+
+ case Screen:
+ m.Type = "screen"
+ m.MessageId = makeMessageId(m.MessageId, id)
+ m.Timestamp = makeTimestamp(m.Timestamp, ts)
+ msg = m
+
+ case Track:
+ m.Type = "track"
+ m.MessageId = makeMessageId(m.MessageId, id)
+ m.Timestamp = makeTimestamp(m.Timestamp, ts)
+ msg = m
+ }
+
+ defer func() {
+ // When the `msgs` channel is closed writing to it will trigger a panic.
+ // To avoid letting the panic propagate to the caller we recover from it
+ // and instead report that the client has been closed and shouldn't be
+ // used anymore.
+ if recover() != nil {
+ err = ErrClosed
+ }
+ }()
+
+ c.msgs <- msg
+ return
+}
+
+// Close and flush metrics.
+func (c *client) Close() (err error) {
+ defer func() {
+ // Always recover, a panic could be raised if `c`.quit was closed which
+ // means the method was called more than once.
+ if recover() != nil {
+ err = ErrClosed
+ }
+ }()
+ close(c.quit)
+ <-c.shutdown
+ return
+}
+
+// Asychronously send a batched requests.
+func (c *client) sendAsync(msgs []message, wg *sync.WaitGroup, ex *executor) {
+ wg.Add(1)
+
+ if !ex.do(func() {
+ defer wg.Done()
+ defer func() {
+ // In case a bug is introduced in the send function that triggers
+ // a panic, we don't want this to ever crash the application so we
+ // catch it here and log it instead.
+ if err := recover(); err != nil {
+ c.errorf("panic - %s", err)
+ }
+ }()
+ c.send(msgs)
+ }) {
+ wg.Done()
+ c.errorf("sending messages failed - %s", ErrTooManyRequests)
+ c.notifyFailure(msgs, ErrTooManyRequests)
+ }
+}
+
+// Send batch request.
+func (c *client) send(msgs []message) {
+ const attempts = 10
+
+ b, err := json.Marshal(batch{
+ MessageId: c.uid(),
+ SentAt: c.now(),
+ Messages: msgs,
+ Context: c.DefaultContext,
+ })
+
+ if err != nil {
+ c.errorf("marshalling messages - %s", err)
+ c.notifyFailure(msgs, err)
+ return
+ }
+
+ for i := 0; i != attempts; i++ {
+ if err = c.upload(b); err == nil {
+ c.notifySuccess(msgs)
+ return
+ }
+
+ // Wait for either a retry timeout or the client to be closed.
+ select {
+ case <-time.After(c.RetryAfter(i)):
+ case <-c.quit:
+ c.errorf("%d messages dropped because they failed to be sent and the client was closed", len(msgs))
+ c.notifyFailure(msgs, err)
+ return
+ }
+ }
+
+ c.errorf("%d messages dropped because they failed to be sent after %d attempts", len(msgs), attempts)
+ c.notifyFailure(msgs, err)
+}
+
+// Upload serialized batch message.
+func (c *client) upload(b []byte) error {
+ url := c.Endpoint + "/v1/batch"
+ req, err := http.NewRequest("POST", url, bytes.NewReader(b))
+ if err != nil {
+ c.errorf("creating request - %s", err)
+ return err
+ }
+
+ req.Header.Add("User-Agent", "analytics-go (version: "+Version+")")
+ req.Header.Add("Content-Type", "application/json")
+ req.Header.Add("Content-Length", string(len(b)))
+ req.SetBasicAuth(c.key, "")
+
+ res, err := c.http.Do(req)
+
+ if err != nil {
+ c.errorf("sending request - %s", err)
+ return err
+ }
+
+ defer res.Body.Close()
+ return c.report(res)
+}
+
+// Report on response body.
+func (c *client) report(res *http.Response) (err error) {
+ var body []byte
+
+ if res.StatusCode < 300 {
+ c.debugf("response %s", res.Status)
+ return
+ }
+
+ if body, err = ioutil.ReadAll(res.Body); err != nil {
+ c.errorf("response %d %s - %s", res.StatusCode, res.Status, err)
+ return
+ }
+
+ c.logf("response %d %s – %s", res.StatusCode, res.Status, string(body))
+ return fmt.Errorf("%d %s", res.StatusCode, res.Status)
+}
+
+// Batch loop.
+func (c *client) loop() {
+ defer close(c.shutdown)
+
+ wg := &sync.WaitGroup{}
+ defer wg.Wait()
+
+ tick := time.NewTicker(c.Interval)
+ defer tick.Stop()
+
+ ex := newExecutor(c.maxConcurrentRequests)
+ defer ex.close()
+
+ mq := messageQueue{
+ maxBatchSize: c.BatchSize,
+ maxBatchBytes: c.maxBatchBytes(),
+ }
+
+ for {
+ select {
+ case msg := <-c.msgs:
+ c.push(&mq, msg, wg, ex)
+
+ case <-tick.C:
+ c.flush(&mq, wg, ex)
+
+ case <-c.quit:
+ c.debugf("exit requested – draining messages")
+
+ // Drain the msg channel, we have to close it first so no more
+ // messages can be pushed and otherwise the loop would never end.
+ close(c.msgs)
+ for msg := range c.msgs {
+ c.push(&mq, msg, wg, ex)
+ }
+
+ c.flush(&mq, wg, ex)
+ c.debugf("exit")
+ return
+ }
+ }
+}
+
+func (c *client) push(q *messageQueue, m Message, wg *sync.WaitGroup, ex *executor) {
+ var msg message
+ var err error
+
+ if msg, err = makeMessage(m, maxMessageBytes); err != nil {
+ c.errorf("%s - %v", err, m)
+ c.notifyFailure([]message{{m, nil}}, err)
+ return
+ }
+
+ c.debugf("buffer (%d/%d) %v", len(q.pending), c.BatchSize, m)
+
+ if msgs := q.push(msg); msgs != nil {
+ c.debugf("exceeded messages batch limit with batch of %d messages – flushing", len(msgs))
+ c.sendAsync(msgs, wg, ex)
+ }
+}
+
+func (c *client) flush(q *messageQueue, wg *sync.WaitGroup, ex *executor) {
+ if msgs := q.flush(); msgs != nil {
+ c.debugf("flushing %d messages", len(msgs))
+ c.sendAsync(msgs, wg, ex)
+ }
+}
+
+func (c *client) debugf(format string, args ...interface{}) {
+ if c.Verbose {
+ c.logf(format, args...)
+ }
+}
+
+func (c *client) logf(format string, args ...interface{}) {
+ c.Logger.Logf(format, args...)
+}
+
+func (c *client) errorf(format string, args ...interface{}) {
+ c.Logger.Errorf(format, args...)
+}
+
+func (c *client) maxBatchBytes() int {
+ b, _ := json.Marshal(batch{
+ MessageId: c.uid(),
+ SentAt: c.now(),
+ Context: c.DefaultContext,
+ })
+ return maxBatchBytes - len(b)
+}
+
+func (c *client) notifySuccess(msgs []message) {
+ if c.Callback != nil {
+ for _, m := range msgs {
+ c.Callback.Success(m.msg)
+ }
+ }
+}
+
+func (c *client) notifyFailure(msgs []message, err error) {
+ if c.Callback != nil {
+ for _, m := range msgs {
+ c.Callback.Failure(m.msg, err)
+ }
+ }
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/config.go b/vendor/gopkg.in/segmentio/analytics-go.v3/config.go
new file mode 100644
index 00000000000..c1677573042
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/config.go
@@ -0,0 +1,173 @@
+package analytics
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/segmentio/backo-go"
+ "github.com/xtgo/uuid"
+)
+
+// Instances of this type carry the different configuration options that may
+// be set when instantiating a client.
+//
+// Each field's zero-value is either meaningful or interpreted as using the
+// default value defined by the library.
+type Config struct {
+
+ // The endpoint to which the client connect and send their messages, set to
+ // `DefaultEndpoint` by default.
+ Endpoint string
+
+ // The flushing interval of the client. Messages will be sent when they've
+ // been queued up to the maximum batch size or when the flushing interval
+ // timer triggers.
+ Interval time.Duration
+
+ // The HTTP transport used by the client, this allows an application to
+ // redefine how requests are being sent at the HTTP level (for example,
+ // to change the connection pooling policy).
+ // If none is specified the client uses `http.DefaultTransport`.
+ Transport http.RoundTripper
+
+ // The logger used by the client to output info or error messages when that
+ // are generated by background operations.
+ // If none is specified the client uses a standard logger that outputs to
+ // `os.Stderr`.
+ Logger Logger
+
+ // The callback object that will be used by the client to notify the
+ // application when messages sends to the backend API succeeded or failed.
+ Callback Callback
+
+ // The maximum number of messages that will be sent in one API call.
+ // Messages will be sent when they've been queued up to the maximum batch
+ // size or when the flushing interval timer triggers.
+ // Note that the API will still enforce a 500KB limit on each HTTP request
+ // which is independent from the number of embedded messages.
+ BatchSize int
+
+ // When set to true the client will send more frequent and detailed messages
+ // to its logger.
+ Verbose bool
+
+ // The default context set on each message sent by the client.
+ DefaultContext *Context
+
+ // The retry policy used by the client to resend requests that have failed.
+ // The function is called with how many times the operation has been retried
+ // and is expected to return how long the client should wait before trying
+ // again.
+ // If not set the client will fallback to use a default retry policy.
+ RetryAfter func(int) time.Duration
+
+ // A function called by the client to generate unique message identifiers.
+ // The client uses a UUID generator if none is provided.
+ // This field is not exported and only exposed internally to let unit tests
+ // mock the id generation.
+ uid func() string
+
+ // A function called by the client to get the current time, `time.Now` is
+ // used by default.
+ // This field is not exported and only exposed internally to let unit tests
+ // mock the current time.
+ now func() time.Time
+
+ // The maximum number of goroutines that will be spawned by a client to send
+ // requests to the backend API.
+ // This field is not exported and only exposed internally to let unit tests
+ // mock the current time.
+ maxConcurrentRequests int
+}
+
+// This constant sets the default endpoint to which client instances send
+// messages if none was explictly set.
+const DefaultEndpoint = "https://api.segment.io"
+
+// This constant sets the default flush interval used by client instances if
+// none was explicitly set.
+const DefaultInterval = 5 * time.Second
+
+// This constant sets the default batch size used by client instances if none
+// was explicitly set.
+const DefaultBatchSize = 250
+
+// Verifies that fields that don't have zero-values are set to valid values,
+// returns an error describing the problem if a field was invalid.
+func (c *Config) validate() error {
+ if c.Interval < 0 {
+ return ConfigError{
+ Reason: "negative time intervals are not supported",
+ Field: "Interval",
+ Value: c.Interval,
+ }
+ }
+
+ if c.BatchSize < 0 {
+ return ConfigError{
+ Reason: "negative batch sizes are not supported",
+ Field: "BatchSize",
+ Value: c.BatchSize,
+ }
+ }
+
+ return nil
+}
+
+// Given a config object as argument the function will set all zero-values to
+// their defaults and return the modified object.
+func makeConfig(c Config) Config {
+ if len(c.Endpoint) == 0 {
+ c.Endpoint = DefaultEndpoint
+ }
+
+ if c.Interval == 0 {
+ c.Interval = DefaultInterval
+ }
+
+ if c.Transport == nil {
+ c.Transport = http.DefaultTransport
+ }
+
+ if c.Logger == nil {
+ c.Logger = newDefaultLogger()
+ }
+
+ if c.BatchSize == 0 {
+ c.BatchSize = DefaultBatchSize
+ }
+
+ if c.DefaultContext == nil {
+ c.DefaultContext = &Context{}
+ }
+
+ if c.RetryAfter == nil {
+ c.RetryAfter = backo.DefaultBacko().Duration
+ }
+
+ if c.uid == nil {
+ c.uid = uid
+ }
+
+ if c.now == nil {
+ c.now = time.Now
+ }
+
+ if c.maxConcurrentRequests == 0 {
+ c.maxConcurrentRequests = 1000
+ }
+
+ // We always overwrite the 'library' field of the default context set on the
+ // client because we want this information to be accurate.
+ c.DefaultContext.Library = LibraryInfo{
+ Name: "analytics-go",
+ Version: Version,
+ }
+ return c
+}
+
+// This function returns a string representation of a UUID, it's the default
+// function used for generating unique IDs.
+func uid() string {
+ return uuid.NewRandom().String()
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/context.go b/vendor/gopkg.in/segmentio/analytics-go.v3/context.go
new file mode 100644
index 00000000000..7a30c1b98d0
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/context.go
@@ -0,0 +1,148 @@
+package analytics
+
+import (
+ "encoding/json"
+ "net"
+ "reflect"
+)
+
+// This type provides the representation of the `context` object as defined in
+// https://segment.com/docs/spec/common/#context
+type Context struct {
+ App AppInfo `json:"app,omitempty"`
+ Campaign CampaignInfo `json:"campaign,omitempty"`
+ Device DeviceInfo `json:"device,omitempty"`
+ Library LibraryInfo `json:"library,omitempty"`
+ Location LocationInfo `json:"location,omitempty"`
+ Network NetworkInfo `json:"network,omitempty"`
+ OS OSInfo `json:"os,omitempty"`
+ Page PageInfo `json:"page,omitempty"`
+ Referrer ReferrerInfo `json:"referrer,omitempty"`
+ Screen ScreenInfo `json:"screen,omitempty"`
+ IP net.IP `json:"ip,omitempty"`
+ Locale string `json:"locale,omitempty"`
+ Timezone string `json:"timezone,omitempty"`
+ UserAgent string `json:"userAgent,omitempty"`
+ Traits Traits `json:"traits,omitempty"`
+
+ // This map is used to allow extensions to the context specifications that
+ // may not be documented or could be introduced in the future.
+ // The fields of this map are inlined in the serialized context object,
+ // there is no actual "extra" field in the JSON representation.
+ Extra map[string]interface{} `json:"-"`
+}
+
+// This type provides the representation of the `context.app` object as defined
+// in https://segment.com/docs/spec/common/#context
+type AppInfo struct {
+ Name string `json:"name,omitempty"`
+ Version string `json:"version,omitempty"`
+ Build string `json:"build,omitempty"`
+ Namespace string `json:"namespace,omitempty"`
+}
+
+// This type provides the representation of the `context.campaign` object as
+// defined in https://segment.com/docs/spec/common/#context
+type CampaignInfo struct {
+ Name string `json:"name,omitempty"`
+ Source string `json:"source,omitempty"`
+ Medium string `json:"medium,omitempty"`
+ Term string `json:"term,omitempty"`
+ Content string `json:"content,omitempty"`
+}
+
+// This type provides the representation of the `context.device` object as
+// defined in https://segment.com/docs/spec/common/#context
+type DeviceInfo struct {
+ Id string `json:"id,omitempty"`
+ Manufacturer string `json:"manufacturer,omitempty"`
+ Model string `json:"model,omitempty"`
+ Name string `json:"name,omitempty"`
+ Type string `json:"type,omitempty"`
+ Version string `json:"version,omitempty"`
+ AdvertisingID string `json:"advertisingId,omitempty"`
+}
+
+// This type provides the representation of the `context.library` object as
+// defined in https://segment.com/docs/spec/common/#context
+type LibraryInfo struct {
+ Name string `json:"name,omitempty"`
+ Version string `json:"version,omitempty"`
+}
+
+// This type provides the representation of the `context.location` object as
+// defined in https://segment.com/docs/spec/common/#context
+type LocationInfo struct {
+ City string `json:"city,omitempty"`
+ Country string `json:"country,omitempty"`
+ Region string `json:"region,omitempty"`
+ Latitude float64 `json:"latitude,omitempty"`
+ Longitude float64 `json:"longitude,omitempty"`
+ Speed float64 `json:"speed,omitempty"`
+}
+
+// This type provides the representation of the `context.network` object as
+// defined in https://segment.com/docs/spec/common/#context
+type NetworkInfo struct {
+ Bluetooth bool `json:"bluetooth,omitempty"`
+ Cellular bool `json:"cellular,omitempty"`
+ WIFI bool `json:"wifi,omitempty"`
+ Carrier string `json:"carrier,omitempty"`
+}
+
+// This type provides the representation of the `context.os` object as defined
+// in https://segment.com/docs/spec/common/#context
+type OSInfo struct {
+ Name string `json:"name,omitempty"`
+ Version string `json:"version,omitempty"`
+}
+
+// This type provides the representation of the `context.page` object as
+// defined in https://segment.com/docs/spec/common/#context
+type PageInfo struct {
+ Hash string `json:"hash,omitempty"`
+ Path string `json:"path,omitempty"`
+ Referrer string `json:"referrer,omitempty"`
+ Search string `json:"search,omitempty"`
+ Title string `json:"title,omitempty"`
+ URL string `json:"url,omitempty"`
+}
+
+// This type provides the representation of the `context.referrer` object as
+// defined in https://segment.com/docs/spec/common/#context
+type ReferrerInfo struct {
+ Type string `json:"type,omitempty"`
+ Name string `json:"name,omitempty"`
+ URL string `json:"url,omitempty"`
+ Link string `json:"link,omitempty"`
+}
+
+// This type provides the representation of the `context.screen` object as
+// defined in https://segment.com/docs/spec/common/#context
+type ScreenInfo struct {
+ Density int `json:"density,omitempty"`
+ Width int `json:"width,omitempty"`
+ Height int `json:"height,omitempty"`
+}
+
+// Satisfy the `json.Marshaler` interface. We have to flatten out the `Extra`
+// field but the standard json package doesn't support it yet.
+// Implementing this interface allows us to override the default marshaling of
+// the context object and to the inlining ourselves.
+//
+// Related discussion: https://github.com/golang/go/issues/6213
+func (ctx Context) MarshalJSON() ([]byte, error) {
+ v := reflect.ValueOf(ctx)
+ n := v.NumField()
+ m := make(map[string]interface{}, n+len(ctx.Extra))
+
+ // Copy the `Extra` map into the map representation of the context, it is
+ // important to do this operation before going through the actual struct
+ // fields so the latter take precendence and override duplicated values
+ // that would be set in the extensions.
+ for name, value := range ctx.Extra {
+ m[name] = value
+ }
+
+ return json.Marshal(structToMap(v, m))
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/error.go b/vendor/gopkg.in/segmentio/analytics-go.v3/error.go
new file mode 100644
index 00000000000..d5503864e91
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/error.go
@@ -0,0 +1,60 @@
+package analytics
+
+import (
+ "errors"
+ "fmt"
+)
+
+// Returned by the `NewWithConfig` function when the one of the configuration
+// fields was set to an impossible value (like a negative duration).
+type ConfigError struct {
+
+ // A human-readable message explaining why the configuration field's value
+ // is invalid.
+ Reason string
+
+ // The name of the configuration field that was carrying an invalid value.
+ Field string
+
+ // The value of the configuration field that caused the error.
+ Value interface{}
+}
+
+func (e ConfigError) Error() string {
+ return fmt.Sprintf("analytics.NewWithConfig: %s (analytics.Config.%s: %#v)", e.Reason, e.Field, e.Value)
+}
+
+// Instances of this type are used to represent errors returned when a field was
+// no initialize properly in a structure passed as argument to one of the
+// functions of this package.
+type FieldError struct {
+
+ // The human-readable representation of the type of structure that wasn't
+ // initialized properly.
+ Type string
+
+ // The name of the field that wasn't properly initialized.
+ Name string
+
+ // The value of the field that wasn't properly initialized.
+ Value interface{}
+}
+
+func (e FieldError) Error() string {
+ return fmt.Sprintf("%s.%s: invalid field value: %#v", e.Type, e.Name, e.Value)
+}
+
+var (
+ // This error is returned by methods of the `Client` interface when they are
+ // called after the client was already closed.
+ ErrClosed = errors.New("the client was already closed")
+
+ // This error is used to notify the application that too many requests are
+ // already being sent and no more messages can be accepted.
+ ErrTooManyRequests = errors.New("too many requests are already in-flight")
+
+ // This error is used to notify the client callbacks that a message send
+ // failed because the JSON representation of a message exceeded the upper
+ // limit.
+ ErrMessageTooBig = errors.New("the message exceeds the maximum allowed size")
+)
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/executor.go b/vendor/gopkg.in/segmentio/analytics-go.v3/executor.go
new file mode 100644
index 00000000000..405ee98eef2
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/executor.go
@@ -0,0 +1,53 @@
+package analytics
+
+import "sync"
+
+type executor struct {
+ queue chan func()
+ mutex sync.Mutex
+ size int
+ cap int
+}
+
+func newExecutor(cap int) *executor {
+ e := &executor{
+ queue: make(chan func(), 1),
+ cap: cap,
+ }
+ go e.loop()
+ return e
+}
+
+func (e *executor) do(task func()) (ok bool) {
+ e.mutex.Lock()
+
+ if e.size != e.cap {
+ e.queue <- task
+ e.size++
+ ok = true
+ }
+
+ e.mutex.Unlock()
+ return
+}
+
+func (e *executor) close() {
+ close(e.queue)
+}
+
+func (e *executor) loop() {
+ for task := range e.queue {
+ go e.run(task)
+ }
+}
+
+func (e *executor) run(task func()) {
+ defer e.done()
+ task()
+}
+
+func (e *executor) done() {
+ e.mutex.Lock()
+ e.size--
+ e.mutex.Unlock()
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/group.go b/vendor/gopkg.in/segmentio/analytics-go.v3/group.go
new file mode 100644
index 00000000000..e8b7af6b8ff
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/group.go
@@ -0,0 +1,40 @@
+package analytics
+
+import "time"
+
+// This type represents object sent in a group call as described in
+// https://segment.com/docs/libraries/http/#group
+type Group struct {
+ // This field is exported for serialization purposes and shouldn't be set by
+ // the application, its value is always overwritten by the library.
+ Type string `json:"type,omitempty"`
+
+ MessageId string `json:"messageId,omitempty"`
+ AnonymousId string `json:"anonymousId,omitempty"`
+ UserId string `json:"userId,omitempty"`
+ GroupId string `json:"groupId"`
+ Timestamp time.Time `json:"timestamp,omitempty"`
+ Context *Context `json:"context,omitempty"`
+ Traits Traits `json:"traits,omitempty"`
+ Integrations Integrations `json:"integrations,omitempty"`
+}
+
+func (msg Group) validate() error {
+ if len(msg.GroupId) == 0 {
+ return FieldError{
+ Type: "analytics.Group",
+ Name: "GroupId",
+ Value: msg.GroupId,
+ }
+ }
+
+ if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
+ return FieldError{
+ Type: "analytics.Group",
+ Name: "UserId",
+ Value: msg.UserId,
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/identify.go b/vendor/gopkg.in/segmentio/analytics-go.v3/identify.go
new file mode 100644
index 00000000000..96cbda0ec7f
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/identify.go
@@ -0,0 +1,31 @@
+package analytics
+
+import "time"
+
+// This type represents object sent in an identify call as described in
+// https://segment.com/docs/libraries/http/#identify
+type Identify struct {
+ // This field is exported for serialization purposes and shouldn't be set by
+ // the application, its value is always overwritten by the library.
+ Type string `json:"type,omitempty"`
+
+ MessageId string `json:"messageId,omitempty"`
+ AnonymousId string `json:"anonymousId,omitempty"`
+ UserId string `json:"userId,omitempty"`
+ Timestamp time.Time `json:"timestamp,omitempty"`
+ Context *Context `json:"context,omitempty"`
+ Traits Traits `json:"traits,omitempty"`
+ Integrations Integrations `json:"integrations,omitempty"`
+}
+
+func (msg Identify) validate() error {
+ if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
+ return FieldError{
+ Type: "analytics.Identify",
+ Name: "UserId",
+ Value: msg.UserId,
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/integrations.go b/vendor/gopkg.in/segmentio/analytics-go.v3/integrations.go
new file mode 100644
index 00000000000..407b4912bce
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/integrations.go
@@ -0,0 +1,44 @@
+package analytics
+
+// This type is used to represent integrations in messages that support it.
+// It is a free-form where values are most often booleans that enable or
+// disable integrations.
+// Here's a quick example of how this type is meant to be used:
+//
+// analytics.Track{
+// UserId: "0123456789",
+// Integrations: analytics.NewIntegrations()
+// .EnableAll()
+// .Disable("Salesforce")
+// .Disable("Marketo"),
+// }
+//
+// The specifications can be found at https://segment.com/docs/spec/common/#integrations
+type Integrations map[string]interface{}
+
+func NewIntegrations() Integrations {
+ return make(Integrations, 10)
+}
+
+func (i Integrations) EnableAll() Integrations {
+ return i.Enable("all")
+}
+
+func (i Integrations) DisableAll() Integrations {
+ return i.Disable("all")
+}
+
+func (i Integrations) Enable(name string) Integrations {
+ return i.Set(name, true)
+}
+
+func (i Integrations) Disable(name string) Integrations {
+ return i.Set(name, false)
+}
+
+// Sets an integration named by the first argument to the specified value, any
+// value other than `false` will be interpreted as enabling the integration.
+func (i Integrations) Set(name string, value interface{}) Integrations {
+ i[name] = value
+ return i
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/json.go b/vendor/gopkg.in/segmentio/analytics-go.v3/json.go
new file mode 100644
index 00000000000..cd3b1752ef2
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/json.go
@@ -0,0 +1,87 @@
+package analytics
+
+import (
+ "reflect"
+ "strings"
+)
+
+// Imitate what what the JSON package would do when serializing a struct value,
+// the only difference is we we don't serialize zero-value struct fields as well.
+// Note that this function doesn't recursively convert structures to maps, only
+// the value passed as argument is transformed.
+func structToMap(v reflect.Value, m map[string]interface{}) map[string]interface{} {
+ t := v.Type()
+ n := t.NumField()
+
+ if m == nil {
+ m = make(map[string]interface{}, n)
+ }
+
+ for i := 0; i != n; i++ {
+ field := t.Field(i)
+ value := v.Field(i)
+ name, omitempty := parseJsonTag(field.Tag.Get("json"), field.Name)
+
+ if name != "-" && !(omitempty && isZeroValue(value)) {
+ m[name] = value.Interface()
+ }
+ }
+
+ return m
+}
+
+// Parses a JSON tag the way the json package would do it, returing the expected
+// name of the field once serialized and if empty values should be omitted.
+func parseJsonTag(tag string, defName string) (name string, omitempty bool) {
+ args := strings.Split(tag, ",")
+
+ if len(args) == 0 || len(args[0]) == 0 {
+ name = defName
+ } else {
+ name = args[0]
+ }
+
+ if len(args) > 1 && args[1] == "omitempty" {
+ omitempty = true
+ }
+
+ return
+}
+
+// Checks if the value given as argument is a zero-value, it is based on the
+// isEmptyValue function in https://golang.org/src/encoding/json/encode.go
+// but also checks struct types recursively.
+func isZeroValue(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ return v.Len() == 0
+
+ case reflect.Bool:
+ return !v.Bool()
+
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+
+ case reflect.Float32, reflect.Float64:
+ return v.Float() == 0
+
+ case reflect.Interface, reflect.Ptr:
+ return v.IsNil()
+
+ case reflect.Struct:
+ for i, n := 0, v.NumField(); i != n; i++ {
+ if !isZeroValue(v.Field(i)) {
+ return false
+ }
+ }
+ return true
+
+ case reflect.Invalid:
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/logger.go b/vendor/gopkg.in/segmentio/analytics-go.v3/logger.go
new file mode 100644
index 00000000000..54190c5f390
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/logger.go
@@ -0,0 +1,47 @@
+package analytics
+
+import (
+ "log"
+ "os"
+)
+
+// Instances of types implementing this interface can be used to define where
+// the analytics client logs are written.
+type Logger interface {
+
+ // Analytics clients call this method to log regular messages about the
+ // operations they perform.
+ // Messages logged by this method are usually tagged with an `INFO` log
+ // level in common logging libraries.
+ Logf(format string, args ...interface{})
+
+ // Analytics clients call this method to log errors they encounter while
+ // sending events to the backend servers.
+ // Messages logged by this method are usually tagged with an `ERROR` log
+ // level in common logging libraries.
+ Errorf(format string, args ...interface{})
+}
+
+// This function instantiate an object that statisfies the analytics.Logger
+// interface and send logs to standard logger passed as argument.
+func StdLogger(logger *log.Logger) Logger {
+ return stdLogger{
+ logger: logger,
+ }
+}
+
+type stdLogger struct {
+ logger *log.Logger
+}
+
+func (l stdLogger) Logf(format string, args ...interface{}) {
+ l.logger.Printf("INFO: "+format, args...)
+}
+
+func (l stdLogger) Errorf(format string, args ...interface{}) {
+ l.logger.Printf("ERROR: "+format, args...)
+}
+
+func newDefaultLogger() Logger {
+ return StdLogger(log.New(os.Stderr, "segment ", log.LstdFlags))
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/message.go b/vendor/gopkg.in/segmentio/analytics-go.v3/message.go
new file mode 100644
index 00000000000..2e7b2c96a46
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/message.go
@@ -0,0 +1,128 @@
+package analytics
+
+import (
+ "encoding/json"
+ "time"
+)
+
+// Values implementing this interface are used by analytics clients to notify
+// the application when a message send succeeded or failed.
+//
+// Callback methods are called by a client's internal goroutines, there are no
+// guarantees on which goroutine will trigger the callbacks, the calls can be
+// made sequentially or in parallel, the order doesn't depend on the order of
+// messages were queued to the client.
+//
+// Callback methods must return quickly and not cause long blocking operations
+// to avoid interferring with the client's internal work flow.
+type Callback interface {
+
+ // This method is called for every message that was successfully sent to
+ // the API.
+ Success(Message)
+
+ // This method is called for every message that failed to be sent to the
+ // API and will be discarded by the client.
+ Failure(Message, error)
+}
+
+// This interface is used to represent analytics objects that can be sent via
+// a client.
+//
+// Types like analytics.Track, analytics.Page, etc... implement this interface
+// and therefore can be passed to the analytics.Client.Send method.
+type Message interface {
+
+ // Validates the internal structure of the message, the method must return
+ // nil if the message is valid, or an error describing what went wrong.
+ validate() error
+}
+
+// Takes a message id as first argument and returns it, unless it's the zero-
+// value, in that case the default id passed as second argument is returned.
+func makeMessageId(id string, def string) string {
+ if len(id) == 0 {
+ return def
+ }
+ return id
+}
+
+// Returns the time value passed as first argument, unless it's the zero-value,
+// in that case the default value passed as second argument is returned.
+func makeTimestamp(t time.Time, def time.Time) time.Time {
+ if t == (time.Time{}) {
+ return def
+ }
+ return t
+}
+
+// This structure represents objects sent to the /v1/batch endpoint. We don't
+// export this type because it's only meant to be used internally to send groups
+// of messages in one API call.
+type batch struct {
+ MessageId string `json:"messageId"`
+ SentAt time.Time `json:"sentAt"`
+ Messages []message `json:"batch"`
+ Context *Context `json:"context"`
+}
+
+type message struct {
+ msg Message
+ json []byte
+}
+
+func makeMessage(m Message, maxBytes int) (msg message, err error) {
+ if msg.json, err = json.Marshal(m); err == nil {
+ if len(msg.json) > maxBytes {
+ err = ErrMessageTooBig
+ } else {
+ msg.msg = m
+ }
+ }
+ return
+}
+
+func (m message) MarshalJSON() ([]byte, error) {
+ return m.json, nil
+}
+
+func (m message) size() int {
+ // The `+ 1` is for the comma that sits between each items of a JSON array.
+ return len(m.json) + 1
+}
+
+type messageQueue struct {
+ pending []message
+ bytes int
+ maxBatchSize int
+ maxBatchBytes int
+}
+
+func (q *messageQueue) push(m message) (b []message) {
+ if (q.bytes + m.size()) > q.maxBatchBytes {
+ b = q.flush()
+ }
+
+ if q.pending == nil {
+ q.pending = make([]message, 0, q.maxBatchSize)
+ }
+
+ q.pending = append(q.pending, m)
+ q.bytes += len(m.json)
+
+ if b == nil && len(q.pending) == q.maxBatchSize {
+ b = q.flush()
+ }
+
+ return
+}
+
+func (q *messageQueue) flush() (msgs []message) {
+ msgs, q.pending, q.bytes = q.pending, nil, 0
+ return
+}
+
+const (
+ maxBatchBytes = 500000
+ maxMessageBytes = 15000
+)
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/page.go b/vendor/gopkg.in/segmentio/analytics-go.v3/page.go
new file mode 100644
index 00000000000..f7c64a2a3a4
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/page.go
@@ -0,0 +1,32 @@
+package analytics
+
+import "time"
+
+// This type represents object sent in a page call as described in
+// https://segment.com/docs/libraries/http/#page
+type Page struct {
+ // This field is exported for serialization purposes and shouldn't be set by
+ // the application, its value is always overwritten by the library.
+ Type string `json:"type,omitempty"`
+
+ MessageId string `json:"messageId,omitempty"`
+ AnonymousId string `json:"anonymousId,omitempty"`
+ UserId string `json:"userId,omitempty"`
+ Name string `json:"name,omitempty"`
+ Timestamp time.Time `json:"timestamp,omitempty"`
+ Context *Context `json:"context,omitempty"`
+ Properties Properties `json:"properties,omitempty"`
+ Integrations Integrations `json:"integrations,omitempty"`
+}
+
+func (msg Page) validate() error {
+ if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
+ return FieldError{
+ Type: "analytics.Page",
+ Name: "UserId",
+ Value: msg.UserId,
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/properties.go b/vendor/gopkg.in/segmentio/analytics-go.v3/properties.go
new file mode 100644
index 00000000000..8b218aebdcf
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/properties.go
@@ -0,0 +1,117 @@
+package analytics
+
+// This type is used to represent properties in messages that support it.
+// It is a free-form object so the application can set any value it sees fit but
+// a few helper method are defined to make it easier to instantiate properties with
+// common fields.
+// Here's a quick example of how this type is meant to be used:
+//
+// analytics.Page{
+// UserId: "0123456789",
+// Properties: analytics.NewProperties()
+// .SetRevenue(10.0)
+// .SetCurrency("USD"),
+// }
+//
+type Properties map[string]interface{}
+
+func NewProperties() Properties {
+ return make(Properties, 10)
+}
+
+func (p Properties) SetRevenue(revenue float64) Properties {
+ return p.Set("revenue", revenue)
+}
+
+func (p Properties) SetCurrency(currency string) Properties {
+ return p.Set("currency", currency)
+}
+
+func (p Properties) SetValue(value float64) Properties {
+ return p.Set("value", value)
+}
+
+func (p Properties) SetPath(path string) Properties {
+ return p.Set("path", path)
+}
+
+func (p Properties) SetReferrer(referrer string) Properties {
+ return p.Set("referrer", referrer)
+}
+
+func (p Properties) SetTitle(title string) Properties {
+ return p.Set("title", title)
+}
+
+func (p Properties) SetURL(url string) Properties {
+ return p.Set("url", url)
+}
+
+func (p Properties) SetName(name string) Properties {
+ return p.Set("name", name)
+}
+
+func (p Properties) SetCategory(category string) Properties {
+ return p.Set("category", category)
+}
+
+func (p Properties) SetSKU(sku string) Properties {
+ return p.Set("sku", sku)
+}
+
+func (p Properties) SetPrice(price float64) Properties {
+ return p.Set("price", price)
+}
+
+func (p Properties) SetProductId(id string) Properties {
+ return p.Set("id", id)
+}
+
+func (p Properties) SetOrderId(id string) Properties {
+ return p.Set("orderId", id)
+}
+
+func (p Properties) SetTotal(total float64) Properties {
+ return p.Set("total", total)
+}
+
+func (p Properties) SetSubtotal(subtotal float64) Properties {
+ return p.Set("subtotal", subtotal)
+}
+
+func (p Properties) SetShipping(shipping float64) Properties {
+ return p.Set("shipping", shipping)
+}
+
+func (p Properties) SetTax(tax float64) Properties {
+ return p.Set("tax", tax)
+}
+
+func (p Properties) SetDiscount(discount float64) Properties {
+ return p.Set("discount", discount)
+}
+
+func (p Properties) SetCoupon(coupon string) Properties {
+ return p.Set("coupon", coupon)
+}
+
+func (p Properties) SetProducts(products ...Product) Properties {
+ return p.Set("products", products)
+}
+
+func (p Properties) SetRepeat(repeat bool) Properties {
+ return p.Set("repeat", repeat)
+}
+
+func (p Properties) Set(name string, value interface{}) Properties {
+ p[name] = value
+ return p
+}
+
+// This type represents products in the E-commerce API.
+type Product struct {
+ ID string `json:"id,omitempty"`
+ SKU string `json:"sky,omitempty"`
+ Name string `json:"name,omitempty"`
+ Price float64 `json:"price"`
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/screen.go b/vendor/gopkg.in/segmentio/analytics-go.v3/screen.go
new file mode 100644
index 00000000000..9e9849eb00a
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/screen.go
@@ -0,0 +1,32 @@
+package analytics
+
+import "time"
+
+// This type represents object sent in a screen call as described in
+// https://segment.com/docs/libraries/http/#screen
+type Screen struct {
+ // This field is exported for serialization purposes and shouldn't be set by
+ // the application, its value is always overwritten by the library.
+ Type string `json:"type,omitempty"`
+
+ MessageId string `json:"messageId,omitempty"`
+ AnonymousId string `json:"anonymousId,omitempty"`
+ UserId string `json:"userId,omitempty"`
+ Name string `json:"name,omitempty"`
+ Timestamp time.Time `json:"timestamp,omitempty"`
+ Context *Context `json:"context,omitempty"`
+ Properties Properties `json:"properties,omitempty"`
+ Integrations Integrations `json:"integrations,omitempty"`
+}
+
+func (msg Screen) validate() error {
+ if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
+ return FieldError{
+ Type: "analytics.Screen",
+ Name: "UserId",
+ Value: msg.UserId,
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/timeout_15.go b/vendor/gopkg.in/segmentio/analytics-go.v3/timeout_15.go
new file mode 100644
index 00000000000..12b963eddb8
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/timeout_15.go
@@ -0,0 +1,16 @@
+// +build !go1.6
+
+package analytics
+
+import "net/http"
+
+// http clients on versions of go before 1.6 only support timeout if the
+// transport implements the `CancelRequest` method.
+func supportsTimeout(transport http.RoundTripper) bool {
+ _, ok := transport.(requestCanceler)
+ return ok
+}
+
+type requestCanceler interface {
+ CancelRequest(*http.Request)
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/timeout_16.go b/vendor/gopkg.in/segmentio/analytics-go.v3/timeout_16.go
new file mode 100644
index 00000000000..1115cafb772
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/timeout_16.go
@@ -0,0 +1,10 @@
+// +build go1.6
+
+package analytics
+
+import "net/http"
+
+// http clients on versions of go after 1.6 always support timeout.
+func supportsTimeout(transport http.RoundTripper) bool {
+ return true
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/track.go b/vendor/gopkg.in/segmentio/analytics-go.v3/track.go
new file mode 100644
index 00000000000..7d6e40bb1fd
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/track.go
@@ -0,0 +1,40 @@
+package analytics
+
+import "time"
+
+// This type represents object sent in a track call as described in
+// https://segment.com/docs/libraries/http/#track
+type Track struct {
+ // This field is exported for serialization purposes and shouldn't be set by
+ // the application, its value is always overwritten by the library.
+ Type string `json:"type,omitempty"`
+
+ MessageId string `json:"messageId,omitempty"`
+ AnonymousId string `json:"anonymousId,omitempty"`
+ UserId string `json:"userId,omitempty"`
+ Event string `json:"event"`
+ Timestamp time.Time `json:"timestamp,omitempty"`
+ Context *Context `json:"context,omitempty"`
+ Properties Properties `json:"properties,omitempty"`
+ Integrations Integrations `json:"integrations,omitempty"`
+}
+
+func (msg Track) validate() error {
+ if len(msg.Event) == 0 {
+ return FieldError{
+ Type: "analytics.Track",
+ Name: "Event",
+ Value: msg.Event,
+ }
+ }
+
+ if len(msg.UserId) == 0 && len(msg.AnonymousId) == 0 {
+ return FieldError{
+ Type: "analytics.Track",
+ Name: "UserId",
+ Value: msg.UserId,
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/gopkg.in/segmentio/analytics-go.v3/traits.go b/vendor/gopkg.in/segmentio/analytics-go.v3/traits.go
new file mode 100644
index 00000000000..d4e82f07a7a
--- /dev/null
+++ b/vendor/gopkg.in/segmentio/analytics-go.v3/traits.go
@@ -0,0 +1,89 @@
+package analytics
+
+import "time"
+
+// This type is used to represent traits in messages that support it.
+// It is a free-form object so the application can set any value it sees fit but
+// a few helper method are defined to make it easier to instantiate traits with
+// common fields.
+// Here's a quick example of how this type is meant to be used:
+//
+// analytics.Identify{
+// UserId: "0123456789",
+// Traits: analytics.NewTraits()
+// .SetFirstName("Luke")
+// .SetLastName("Skywalker")
+// .Set("Role", "Jedi"),
+// }
+//
+// The specifications can be found at https://segment.com/docs/spec/identify/#traits
+type Traits map[string]interface{}
+
+func NewTraits() Traits {
+ return make(Traits, 10)
+}
+
+func (t Traits) SetAddress(address string) Traits {
+ return t.Set("address", address)
+}
+
+func (t Traits) SetAge(age int) Traits {
+ return t.Set("age", age)
+}
+
+func (t Traits) SetAvatar(url string) Traits {
+ return t.Set("avatar", url)
+}
+
+func (t Traits) SetBirthday(date time.Time) Traits {
+ return t.Set("birthday", date)
+}
+
+func (t Traits) SetCreatedAt(date time.Time) Traits {
+ return t.Set("createdAt", date)
+}
+
+func (t Traits) SetDescription(desc string) Traits {
+ return t.Set("description", desc)
+}
+
+func (t Traits) SetEmail(email string) Traits {
+ return t.Set("email", email)
+}
+
+func (t Traits) SetFirstName(firstName string) Traits {
+ return t.Set("firstName", firstName)
+}
+
+func (t Traits) SetGender(gender string) Traits {
+ return t.Set("gender", gender)
+}
+
+func (t Traits) SetLastName(lastName string) Traits {
+ return t.Set("lastName", lastName)
+}
+
+func (t Traits) SetName(name string) Traits {
+ return t.Set("name", name)
+}
+
+func (t Traits) SetPhone(phone string) Traits {
+ return t.Set("phone", phone)
+}
+
+func (t Traits) SetTitle(title string) Traits {
+ return t.Set("title", title)
+}
+
+func (t Traits) SetUsername(username string) Traits {
+ return t.Set("username", username)
+}
+
+func (t Traits) SetWebsite(url string) Traits {
+ return t.Set("website", url)
+}
+
+func (t Traits) Set(field string, value interface{}) Traits {
+ t[field] = value
+ return t
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 371607543e5..67612838d0a 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -53,6 +53,8 @@ github.com/cheggaaa/pb
github.com/containerd/continuity/pathdriver
# github.com/davecgh/go-spew v1.1.1
github.com/davecgh/go-spew/spew
+# github.com/denisbrodbeck/machineid v1.0.1
+github.com/denisbrodbeck/machineid
# github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23
github.com/docker/docker/api/types/registry
github.com/docker/docker/api/types/swarm
@@ -159,6 +161,8 @@ github.com/pelletier/go-toml
github.com/pkg/errors
# github.com/pmezard/go-difflib v1.0.0
github.com/pmezard/go-difflib/difflib
+# github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c
+github.com/segmentio/backo-go
# github.com/sirupsen/logrus v1.0.6
github.com/sirupsen/logrus
# github.com/spf13/afero v1.1.1
@@ -177,15 +181,20 @@ github.com/spf13/viper
# github.com/stretchr/testify v1.2.2
github.com/stretchr/testify/assert
github.com/stretchr/testify/require
+# github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c
+github.com/xtgo/uuid
# golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac
golang.org/x/crypto/ssh/terminal
golang.org/x/crypto/scrypt
golang.org/x/crypto/pbkdf2
# golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e
+golang.org/x/sys/windows/registry
golang.org/x/sys/unix
golang.org/x/sys/windows
# golang.org/x/text v0.3.0
golang.org/x/text/transform
golang.org/x/text/unicode/norm
+# gopkg.in/segmentio/analytics-go.v3 v3.0.1
+gopkg.in/segmentio/analytics-go.v3
# gopkg.in/yaml.v2 v2.2.1
gopkg.in/yaml.v2