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