Enter Telemetry (#4482)

* Send telemetry to segment

* Add tests and other properties

* Add comments

* Fix fmt

* Add telemetry dev key

* Add non-test related changes requested to the PR and add consenttelemetry prompt

* Handle prompt while running tests

* First attempt at fixing unit job failure

* Modify tests as requested

* Stop prompting when preference is called

* Fix comments

* Add more unit tests and integration test(WIP)

* Fix failing tests

* Stop prompting when help is called

* Make tests more verbose

* Add Usage data doc

* Add segment key

* Add a better usage doc

* Add comments, move tests and other requested changes

* Fix comments and remove focus

* Add set ConsentTelemetry unit tests

* Change want from string to bool

* Fix failing integration test

* Fix failing operator hub test

* Fix failing devfile integration test

* Fix tests failing due to cmdwrapper WithEnv

Co-authored-by: Dharmit Shah <shahdharmit@gmail.com>
This commit is contained in:
Parthvi Vala
2021-03-26 17:13:09 +05:30
committed by GitHub
parent 45d4098e38
commit c98445f8bb
102 changed files with 4642 additions and 48012 deletions

29
USAGE_DATA.adoc Normal file
View File

@@ -0,0 +1,29 @@
[[odo]]
= `odo` - Developer-focused CLI for Kubernetes and OpenShift
:toc: macro
:toc-title:
:toclevels: 1
[[usage-data]]
== Usage Data
If the user has consented to `odo` collecting usage data, the following data is collected when a command is executed -
* command's ID
* command's duration time
* command's error message and error type (in case of failure)
* whether the command was run from a terminal
* system information such as OS type
* `odo` version in use
[[enable-disable]]
== Enable/Disable preference
Enable::
`odo preference set ConsentTelemetry true`
Disable::
`odo preference set ConsentTelemetry false`
Note: If earlier the `ConsentTelemetry` preference was enabled, then the data will be collected about the disabling of the preference.

View File

@@ -53,13 +53,14 @@ func main() {
return
}
// Call commands
// checking the value of updatenotification in config
// before proceeding with fetching the latest version
cfg, err := preference.New()
if err != nil {
util.LogErrorAndExit(err, "")
}
// Call commands
// checking the value of updatenotification in config
// before proceeding with fetching the latest version
if cfg.GetUpdateNotification() {
updateInfo := make(chan string)
go version.GetLatestReleaseInfo(updateInfo)

5
go.mod
View File

@@ -18,7 +18,6 @@ require (
github.com/go-git/go-git/v5 v5.2.0
github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe
github.com/golang/mock v1.4.4
github.com/gosimple/slug v1.9.0
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79
github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c
github.com/imdario/mergo v0.3.11 // indirect
@@ -40,20 +39,24 @@ require (
github.com/openshift/library-go v0.0.0-20200407165825-2e79bd232e72
github.com/openshift/oc v0.0.0-alpha.0.0.20200305142246-2576e482bf00
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20200422144016-a6acf50218ed
github.com/pborman/uuid v1.2.0
github.com/pkg/errors v0.9.1
github.com/posener/complete v1.1.2
github.com/redhat-developer/service-binding-operator v0.3.0
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 // indirect
github.com/spf13/afero v1.2.2
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
github.com/tidwall/gjson v1.6.3
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
github.com/zalando/go-keyring v0.1.0
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 // indirect
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221
gopkg.in/AlecAivazis/survey.v1 v1.8.0
gopkg.in/segmentio/analytics-go.v3 v3.1.0
gopkg.in/yaml.v2 v2.3.0
k8s.io/api v0.19.0
k8s.io/apimachinery v0.19.0

32
go.sum
View File

@@ -132,6 +132,7 @@ github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngE
github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
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/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
@@ -217,12 +218,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE=
github.com/deislabs/oras v0.7.0/go.mod h1:sqMKPG3tMyIX9xwXUBRLhZ24o+uT4y6jgBD2RzUTKDM=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/devfile/api v0.0.0-20201103130402-29b8738e196e h1:Ha6wyKs7VWqQTz2Fm1o4PAYpKX6SIiYjgWmGzNW8cBE=
github.com/devfile/api v0.0.0-20201103130402-29b8738e196e/go.mod h1:hp5Lmob7ESmtSZXZ7xRN9o8vemsen9111+ASi2YuXs4=
github.com/devfile/api/v2 v2.0.0-20210211160219-33a78aec06af h1:egbFAAS/CWJMAqa4zIm8Cik3+iCqTAfLPfCj6PLEG5Y=
github.com/devfile/api/v2 v2.0.0-20210211160219-33a78aec06af/go.mod h1:Cot4snybn3qhIh48oIFi9McocnIx7zY5fFbjfrIpPvg=
github.com/devfile/library v0.0.0-20201125155652-6eabeab3ee52 h1:crtWc9SVN0Qw4DkXxD84ChcOomgm0JC/nQ5fKYha4LU=
github.com/devfile/library v0.0.0-20201125155652-6eabeab3ee52/go.mod h1:eQMM2+rxfAZzpG/Ujyoa5yQuezws/q0izcCkTUv0dG8=
github.com/devfile/library v0.0.0-20210216162950-3066a892876c h1:NL9EpuPuFVxCqE00Bh1Uw3YsEqoMdwq4vx0OXSZRVnU=
github.com/devfile/library v0.0.0-20210216162950-3066a892876c/go.mod h1:aGJSpcGrRiYwsQQJMQH1ChHuOptUf49n+j0RDBYyTIQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
@@ -539,8 +536,6 @@ github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gosuri/uitable v0.0.1/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
@@ -616,8 +611,6 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jsonnet-bundler/jsonnet-bundler v0.1.0/go.mod h1:YKsSFc9VFhhLITkJS3X2PrRqWG9u2Jq99udTdDjQLfM=
@@ -779,7 +772,6 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
@@ -790,7 +782,6 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
@@ -819,7 +810,6 @@ github.com/openshift/custom-resource-status v0.0.0-20200602122900-c002fd1547ca/g
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b h1:it0YPE/evO6/m8t8wxis9KFI2F/aleOKsI6d9uz0cEk=
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b/go.mod h1:tNrEB5k8SI+g5kOlsCmL2ELASfpqEofI0+FLBgBdN08=
github.com/openshift/kubernetes v1.17.0-alpha.0.0.20191216151305-079984b0a154/go.mod h1:ypOpFo7ff7y/tpC3zDdQ5NonHlPDTJ6iKnOChMPfloQ=
github.com/openshift/kubernetes-apimachinery v0.0.0-20190820100751-ac02f8882ef6 h1:KRSXk0YTtZ5b8Wwg0H+Pleie6dKjRj6Qnwx6OW398cQ=
github.com/openshift/kubernetes-apimachinery v0.0.0-20191211181342-5a804e65bdc1 h1:pBVMoJWgY4x5R3ZL7U8yXjnIXjUqfv0x21WUU2de3+4=
github.com/openshift/kubernetes-apimachinery v0.0.0-20191211181342-5a804e65bdc1/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
github.com/openshift/kubernetes-cli-runtime v0.0.0-20200114162348-c8810ef308ee h1:Lq+Jnpe8ciNYRm1SyF50Uvk29zybO5g2IgpAi7XGBCI=
@@ -859,6 +849,7 @@ github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776/go.mod h1:3HNVkVOU7vZ
github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
github.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M=
github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@@ -918,8 +909,6 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/prometheus/tsdb v0.8.0/go.mod h1:fSI0j+IUQrDd7+ZtR9WKIGtoYAYAJUKcKhYLG25tN4g=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redhat-developer/service-binding-operator v0.3.0 h1:79FuZinHtInpC5NYmDREiKK8DxmXV1L28OSzENtg8tA=
github.com/redhat-developer/service-binding-operator v0.3.0/go.mod h1:FQgDVBuBvln4TRlPQcTxTnNuJSIhNv3Z/xsMvalLaqo=
@@ -941,6 +930,8 @@ github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHi
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 h1:ZuhckGJ10ulaKkdvJtiAqsLTiPrLaXSdnVgXJKJkTxE=
github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -1042,6 +1033,8 @@ github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
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=
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.6/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
@@ -1150,9 +1143,7 @@ golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
@@ -1209,7 +1200,6 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1219,10 +1209,8 @@ golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1370,6 +1358,8 @@ gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/segmentio/analytics-go.v3 v3.1.0 h1:UzxH1uaGZRpMKDhJyBz0pexz6yUoBU3x8bJsRk/HV6U=
gopkg.in/segmentio/analytics-go.v3 v3.1.0/go.mod h1:4QqqlTlSSpVlWA9/9nDcPw+FkM2yv1NQoYjUbL9/JAw=
gopkg.in/square/go-jose.v1 v1.1.2/go.mod h1:QpYS+a4WhS+DTlyQIi6Ka7MS3SuR9a055rgXNEe6EiA=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@@ -1384,7 +1374,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
@@ -1460,8 +1449,6 @@ k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl
k8s.io/utils v0.0.0-20191010214722-8d271d903fe4/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE=
k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
knative.dev/pkg v0.0.0-20191221032535-9fda5bd59a67/go.mod h1:pgODObA1dTyhNoFxPZTTjNWfx6F0aKsKzn+vaT9XO/Q=
@@ -1478,9 +1465,6 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
rsc.io/letsencrypt v0.0.1/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
sigs.k8s.io/controller-runtime v0.1.10/go.mod h1:HFAYoOh6XMV+jKF1UjFwrknPbowfyHEHHRdJMf2jMX8=
sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
sigs.k8s.io/controller-runtime v0.5.2/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A=
sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvOk9NM=
sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
sigs.k8s.io/controller-runtime v0.6.3 h1:SBbr+inLPEKhvlJtrvDcwIpm+uhDvp63Bl72xYJtoOE=
sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY=
sigs.k8s.io/controller-tools v0.1.10/go.mod h1:6g08p9m9G/So3sBc1AOQifHfhxH/mb6Sc4z0LMI8XMw=

View File

@@ -4,6 +4,15 @@ import (
"flag"
"fmt"
"os"
"strconv"
"time"
"github.com/openshift/odo/pkg/odo/cli/ui"
"gopkg.in/AlecAivazis/survey.v1"
"github.com/openshift/odo/pkg/preference"
"github.com/openshift/odo/pkg/segment"
"k8s.io/klog"
"github.com/openshift/odo/pkg/log"
"github.com/openshift/odo/pkg/odo/util"
@@ -11,6 +20,10 @@ import (
"github.com/spf13/pflag"
)
var (
segmentClient *segment.Client
)
type Runnable interface {
Complete(name string, cmd *cobra.Command, args []string) error
Validate() error
@@ -18,6 +31,33 @@ type Runnable interface {
}
func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
var err error
var startTime time.Time
cfg, _ := preference.New()
// Prompt the user to consent for telemetry if a value is not set already
// Skip prompting if the preference command is called
// This prompt has been placed here so that it does not prompt the user when they call --help
if !cfg.IsSet(preference.ConsentTelemetrySetting) && cmd.Parent().Name() != "preference" {
var consentTelemetry bool
prompt := &survey.Confirm{Message: "Help odo improve by allowing it to collect usage data. Read about our privacy statement: https://developers.redhat.com/article/tool-data-collection. You can change your preference later by changing the ConsentTelemetry preference.", Default: false}
err = survey.AskOne(prompt, &consentTelemetry, nil)
ui.HandleError(err)
if err == nil {
if err1 := cfg.SetConfiguration(preference.ConsentTelemetrySetting, strconv.FormatBool(consentTelemetry)); err1 != nil {
klog.V(4).Info(err1.Error())
}
}
}
// Initiate the segment client if ConsentTelemetry preference is set to true
if cfg.GetConsentTelemetry() {
if segmentClient, err = segment.NewClient(cfg); err != nil {
klog.V(4).Infof("Cannot create a segment client, will not send any data: %s", err.Error())
}
defer segmentClient.Close()
startTime = time.Now()
}
// CheckMachineReadableOutput
// fixes / checks all related machine readable output functions
@@ -26,9 +66,30 @@ func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
// LogErrorAndExit is used so that we get -o (jsonoutput) for cmds which have json output implemented
util.LogErrorAndExit(checkConflictingFlags(cmd), "")
// Run completion, validation and run.
util.LogErrorAndExit(o.Complete(cmd.Name(), cmd, args), "")
util.LogErrorAndExit(o.Validate(), "")
util.LogErrorAndExit(o.Run(), "")
// Only upload data to segment for completion and validation if a non-nil error is returned.
err = o.Complete(cmd.Name(), cmd, args)
if err != nil {
uploadToSegmentAndLog(cmd, err, startTime)
}
err = o.Validate()
if err != nil {
uploadToSegmentAndLog(cmd, err, startTime)
}
uploadToSegmentAndLog(cmd, o.Run(), startTime)
}
// uploadToSegmentAndLog uploads the data to segment and logs the error
func uploadToSegmentAndLog(cmd *cobra.Command, err error, startTime time.Time) {
if segmentClient != nil {
if serr := segmentClient.Upload(cmd.CommandPath(), time.Since(startTime), err); serr != nil {
klog.Errorf("Cannot send data to telemetry: %v", serr)
}
// If the error is not nil, client will be closed so that data can be sent before the program exits.
if err != nil {
segmentClient.Close()
}
}
util.LogErrorAndExit(err, "")
}
// checkConflictingFlags checks for conflicting flags. Currently --context cannot be provided

View File

@@ -120,7 +120,7 @@ func TestGetBuildTimeout(t *testing.T) {
output := cfg.GetBuildTimeout()
if output != tt.want {
t.Errorf("GetBuildTimeout returned unexpected value expected \ngot: %d \nexpected: %d\n", output, tt.want)
t.Errorf("GetBuildTimeout returned unexpected value\ngot: %d \nexpected: %d\n", output, tt.want)
}
})
}
@@ -176,7 +176,7 @@ func TestGetPushTimeout(t *testing.T) {
output := cfg.GetPushTimeout()
if output != tt.want {
t.Errorf("GetPushTimeout returned unexpected value expected \ngot: %d \nexpected: %d\n", output, tt.want)
t.Errorf("GetPushTimeout returned unexpected value\ngot: %d \nexpected: %d\n", output, tt.want)
}
})
}
@@ -232,7 +232,7 @@ func TestGetTimeout(t *testing.T) {
output := cfg.GetTimeout()
if output != tt.want {
t.Errorf("GetTimeout returned unexpected value expected \ngot: %d \nexpected: %d\n", output, tt.want)
t.Errorf("GetTimeout returned unexpected value\ngot: %d \nexpected: %d\n", output, tt.want)
}
})
}
@@ -491,6 +491,33 @@ func TestSetConfiguration(t *testing.T) {
existingConfig: Preference{},
wantErr: true,
},
{
name: fmt.Sprintf("Case 27: set %s to non bool value", ConsentTelemetrySetting),
parameter: ConsentTelemetrySetting,
value: "123",
existingConfig: Preference{},
wantErr: true,
},
{
name: fmt.Sprintf("Case 28: set %s from nil to true", ConsentTelemetrySetting),
parameter: ConsentTelemetrySetting,
value: "true",
existingConfig: Preference{},
wantErr: false,
want: true,
},
{
name: fmt.Sprintf("Case 29: set %s from true to false", ConsentTelemetrySetting),
parameter: ConsentTelemetrySetting,
value: "false",
existingConfig: Preference{
OdoSettings: OdoSettings{
ConsentTelemetry: &trueValue,
},
},
wantErr: false,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -508,23 +535,23 @@ func TestSetConfiguration(t *testing.T) {
switch tt.parameter {
case "updatenotification":
if *cfg.OdoSettings.UpdateNotification != tt.want {
t.Errorf("unexpected value after execution of SetConfiguration \ngot: %t \nexpected: %t\n", *cfg.OdoSettings.UpdateNotification, tt.want)
t.Errorf("unexpected value after execution of SetConfiguration\ngot: %t \nexpected: %t\n", *cfg.OdoSettings.UpdateNotification, tt.want)
}
case "timeout":
if *cfg.OdoSettings.Timeout != tt.want {
t.Errorf("unexpected value after execution of SetConfiguration \ngot: %v \nexpected: %d\n", cfg.OdoSettings.Timeout, tt.want)
t.Errorf("unexpected value after execution of SetConfiguration\ngot: %v \nexpected: %d\n", cfg.OdoSettings.Timeout, tt.want)
}
case "experimental":
if *cfg.OdoSettings.Experimental != tt.want {
t.Errorf("unexpected value after execution of SetConfiguration \ngot: %v \nexpected: %d\n", cfg.OdoSettings.Experimental, tt.want)
t.Errorf("unexpected value after execution of SetConfiguration\ngot: %v \nexpected: %d\n", cfg.OdoSettings.Experimental, tt.want)
}
case "pushtarget":
if *cfg.OdoSettings.PushTarget != tt.want {
t.Errorf("unexpected value after execution of SetConfiguration \ngot: %v \nexpected: %d\n", cfg.OdoSettings.PushTarget, tt.want)
t.Errorf("unexpected value after execution of SetConfiguration\ngot: %v \nexpected: %d\n", cfg.OdoSettings.PushTarget, tt.want)
}
case "registrycachetime":
if *cfg.OdoSettings.RegistryCacheTime != tt.want {
t.Errorf("unexpected value after execution of SetConfiguration \ngot: %v \nexpected: %d\n", *cfg.OdoSettings.RegistryCacheTime, tt.want)
t.Errorf("unexpected value after execution of SetConfiguration\ngot: %v \nexpected: %d\n", *cfg.OdoSettings.RegistryCacheTime, tt.want)
}
}
} else if tt.wantErr && err != nil {
@@ -547,6 +574,61 @@ func TestSetConfiguration(t *testing.T) {
}
}
func TestConsentTelemetry(t *testing.T) {
tempConfigFile, err := ioutil.TempFile("", "odoconfig")
if err != nil {
t.Fatal(err)
}
defer tempConfigFile.Close()
os.Setenv(GlobalConfigEnvName, tempConfigFile.Name())
trueValue := true
falseValue := false
tests := []struct {
name string
existingConfig Preference
want bool
}{
{
name: fmt.Sprintf("Case 1: %s nil", ConsentTelemetrySetting),
existingConfig: Preference{},
want: false,
},
{
name: fmt.Sprintf("Case 2: %s true", ConsentTelemetrySetting),
existingConfig: Preference{
OdoSettings: OdoSettings{
ConsentTelemetry: &trueValue,
},
},
want: true,
},
{
name: fmt.Sprintf("Case 3: %s false", ConsentTelemetrySetting),
existingConfig: Preference{
OdoSettings: OdoSettings{
ConsentTelemetry: &falseValue,
},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := PreferenceInfo{
Preference: tt.existingConfig,
}
output := cfg.GetConsentTelemetry()
if output != tt.want {
t.Errorf("ConsentTelemetry returned unexpected value\ngot: %t \nexpected: %t\n", output, tt.want)
}
})
}
}
func TestGetupdateNotification(t *testing.T) {
tempConfigFile, err := ioutil.TempFile("", "odoconfig")
@@ -596,7 +678,7 @@ func TestGetupdateNotification(t *testing.T) {
output := cfg.GetUpdateNotification()
if output != tt.want {
t.Errorf("GetUpdateNotification returned unexpected value expected \ngot: %t \nexpected: %t\n", output, tt.want)
t.Errorf("GetUpdateNotification returned unexpected value\ngot: %t \nexpected: %t\n", output, tt.want)
}
})
@@ -652,7 +734,7 @@ func TestGetExperimental(t *testing.T) {
output := cfg.GetExperimental()
if output != tt.want {
t.Errorf("GetExperimental returned unexpected value expected \ngot: %t \nexpected: %t\n", output, tt.want)
t.Errorf("GetExperimental returned unexpected value\ngot: %t \nexpected: %t\n", output, tt.want)
}
})
@@ -708,7 +790,7 @@ func TestGetPushTarget(t *testing.T) {
output := cfg.GetPushTarget()
if output != tt.want {
t.Errorf("GetExperimental returned unexpected value expected \ngot: %s \nexpected: %s\n", output, tt.want)
t.Errorf("GetExperimental returned unexpected value\ngot: %s \nexpected: %s\n", output, tt.want)
}
})
@@ -967,7 +1049,7 @@ func TestGetConsentTelemetry(t *testing.T) {
output := cfg.GetConsentTelemetry()
if output != tt.want {
t.Errorf("GetConsentTelemetry returned unexpected value expected \ngot: %t \nexpected: %t\n", output, tt.want)
t.Errorf("GetConsentTelemetry returned unexpected value\ngot: %t \nexpected: %t\n", output, tt.want)
}
})

163
pkg/segment/segment.go Normal file
View File

@@ -0,0 +1,163 @@
package segment
import (
"fmt"
"io/ioutil"
"net"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/openshift/odo/pkg/preference"
"github.com/openshift/odo/pkg/version"
"github.com/pborman/uuid"
"github.com/pkg/errors"
"golang.org/x/term"
"gopkg.in/segmentio/analytics-go.v3"
)
// Writekey will be the API key used to send data to the correct source on Segment
var WriteKey = "R1Z79HadJIrphLoeONZy5uqOjusljSwN"
type Client struct {
// SegmentClient helps interact with the segment API
SegmentClient analytics.Client
// Preference points to the global odo config
Preference *preference.PreferenceInfo
// TelemetryFilePath points to the file containing anonymousID used for tracking odo commands executed by the user
TelemetryFilePath string
}
// NewClient returns a Client created with the default args
func NewClient(preference *preference.PreferenceInfo) (*Client, error) {
homeDir, _ := os.UserHomeDir()
return newCustomClient(preference,
filepath.Join(homeDir, ".redhat", "anonymousId"),
analytics.DefaultEndpoint,
)
}
// newCustomClient returns a Client created with custom args
func newCustomClient(preference *preference.PreferenceInfo, telemetryFilePath string, segmentEndpoint string) (*Client, error) {
// DefaultContext has IP set to 0.0.0.0 so that it does not track user's IP, which it does in case no IP is set
client, err := analytics.NewWithConfig(WriteKey, analytics.Config{
Endpoint: segmentEndpoint,
DefaultContext: &analytics.Context{
IP: net.IPv4(0, 0, 0, 0),
},
})
if err != nil {
return nil, err
}
return &Client{
SegmentClient: client,
Preference: preference,
TelemetryFilePath: telemetryFilePath,
}, nil
}
// Close client connection and send the data
func (c *Client) Close() error {
return c.SegmentClient.Close()
}
// Upload prepares the data to be sent to segment and send it once the client connection closes
func (c *Client) Upload(action string, duration time.Duration, err error) error {
// if the user has not consented for telemetry, return
if !c.Preference.GetConsentTelemetry() {
return nil
}
// obtain the anonymous ID
anonymousID, uerr := getUserIdentity(c.TelemetryFilePath)
if uerr != nil {
return uerr
}
// queue the data that helps identify the user on segment
if err1 := c.SegmentClient.Enqueue(analytics.Identify{
AnonymousId: anonymousID,
Traits: addConfigTraits(),
}); err1 != nil {
return err1
}
// add information to the data
properties := analytics.NewProperties()
// TODO: add other properties when required
properties = properties.Set("version", fmt.Sprintf("odo %v (%v)", version.VERSION, version.GITCOMMIT)).
Set("success", err == nil).
Set("duration(ms)", duration.Milliseconds()).
Set("tty", RunningInTerminal())
// in case the command executed unsuccessfully, add information about the error in the data
if err != nil {
properties = properties.Set("error", SetError(err)).Set("error-type", errorType(err))
}
// queue the data that has telemetry information
return c.SegmentClient.Enqueue(analytics.Track{
AnonymousId: anonymousID,
Event: action,
Properties: properties,
})
}
// addConfigTraits adds information about the system
func addConfigTraits() analytics.Traits {
traits := analytics.NewTraits().Set("os", runtime.GOOS)
return traits
}
// getUserIdentity returns the anonymous ID if it exists, else creates a new one
func getUserIdentity(telemetryFilePath string) (string, error) {
var id []byte
// Get-or-Create the '$HOME/.redhat' directory
if err := os.MkdirAll(filepath.Dir(telemetryFilePath), 0750); err != nil {
return "", err
}
// Get-or-Create the anonymousID file that contains a UUID
if _, err := os.Stat(telemetryFilePath); !os.IsNotExist(err) {
id, err = ioutil.ReadFile(telemetryFilePath)
if err != nil {
return "", err
}
}
// check if the id is a valid uuid, if not, nil is returned
if uuid.Parse(strings.TrimSpace(string(id))) == nil {
id = []byte(uuid.NewRandom().String())
if err := ioutil.WriteFile(telemetryFilePath, id, 0600); err != nil {
return "", err
}
}
return strings.TrimSpace(string(id)), nil
}
// SetError sanitizes any PII(Personally Identifiable Information) from the error
func SetError(err error) string {
// Sanitize user information
user1, err1 := user.Current()
if err1 != nil {
return errors.Wrapf(err1, err1.Error()).Error()
}
return strings.ReplaceAll(err.Error(), user1.Username, "XXXX")
}
// errorType returns the type of error
func errorType(err error) string {
wrappedErr := errors.Unwrap(err)
if wrappedErr != nil {
return fmt.Sprintf("%T", wrappedErr)
}
return fmt.Sprintf("%T", err)
}
// RunningInTerminal checks if odo was run from a terminal
func RunningInTerminal() bool {
return term.IsTerminal(int(os.Stdin.Fd()))
}

225
pkg/segment/segment_test.go Normal file
View File

@@ -0,0 +1,225 @@
package segment
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"os/user"
"runtime"
"strings"
"testing"
"time"
"github.com/openshift/odo/pkg/testingutil/filesystem"
"github.com/openshift/odo/pkg/log"
"github.com/openshift/odo/pkg/preference"
"github.com/openshift/odo/pkg/version"
)
type segmentResponse struct {
Batch []struct {
AnonymousID string `json:"anonymousId"`
MessageId string `json:"messageId"`
Traits struct {
OS string `json:"os"`
} `json:"traits"`
Properties struct {
Error string `json:"error"`
ErrorType string `json:"error-type"`
Success bool `json:"success"`
Version string `json:"version"`
} `json:"properties"`
Type string `json:"type"`
} `json:"batch"`
MessageID string `json:"messageId"`
}
func mockServer() (chan []byte, *httptest.Server) {
done := make(chan []byte, 1)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
bin, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Error(err)
return
}
done <- bin
}))
return done, server
}
func TestClientUploadWithoutConsent(t *testing.T) {
body, server := mockServer()
defer server.Close()
defer close(body)
falseValue := false
cfg := &preference.PreferenceInfo{
Preference: preference.Preference{
OdoSettings: preference.OdoSettings{
ConsentTelemetry: &falseValue,
},
},
}
c, err := newCustomClient(cfg, createConfigDir(t), server.URL)
if err != nil {
t.Error(err)
}
// run a command, odo preference view
if err = c.Upload("odo preference view", time.Second, errors.New("an error occurred")); err != nil {
t.Error(err)
}
if err = c.Close(); err != nil {
t.Error(err)
}
select {
case <-body:
t.Error("server should not receive data")
default:
}
}
func TestClientUploadWithConsent(t *testing.T) {
body, server := mockServer()
defer server.Close()
defer close(body)
trueValue := true
cfg := &preference.PreferenceInfo{
Preference: preference.Preference{
OdoSettings: preference.OdoSettings{
ConsentTelemetry: &trueValue,
},
},
}
tests := []struct {
cmd string
testName string
err error
success bool
errType string
}{
{
testName: "command ran successfully",
err: nil,
success: true,
errType: "",
},
{
testName: "command failed",
err: errors.New("some error occurred"),
success: false,
errType: "*errors.errorString",
},
}
for _, tt := range tests {
t.Log("Running test: ", tt.testName)
t.Run(tt.testName, func(t *testing.T) {
c, err := newCustomClient(cfg, createConfigDir(t), server.URL)
if err != nil {
t.Error(err)
}
// upload the data to Segment
if err := c.Upload("odo create", time.Second, tt.err); err != nil {
t.Error(err)
}
if err := c.Close(); err != nil {
t.Error(err)
}
select {
case x := <-body:
s := segmentResponse{}
if err := json.Unmarshal(x, &s); err != nil {
t.Error(err)
}
// Response returns 2 Batches in response - 1) identify - user's system information,
// and 2) track - information about the fired command
// This checks if both the responses were received
if s.Batch[0].Type != "identify" && s.Batch[1].Type != "track" {
t.Errorf("Missing Identify or Track information.\nIdentify: %v\nTrack:%v", s.Batch[0].Type, s.Batch[1].Type)
}
if s.Batch[0].Traits.OS != runtime.GOOS {
t.Error("OS does not match")
}
if !tt.success {
if s.Batch[1].Properties.Error != tt.err.Error() {
t.Error("Error does not match")
}
} else {
if s.Batch[1].Properties.Error != "" {
t.Error("Error does not match")
}
}
if s.Batch[1].Properties.Success != tt.success {
t.Error("Success does not match")
}
if s.Batch[1].Properties.ErrorType != tt.errType {
t.Error("Error Type does not match")
}
if !strings.Contains(s.Batch[1].Properties.Version, version.VERSION) {
t.Error("Odo version does not match")
}
default:
t.Error("Server should receive data")
}
})
}
}
func TestSetError(t *testing.T) {
user, err := user.Current()
if err != nil {
t.Error(err.Error())
}
tests := []struct {
name string
err error
hasPII bool
}{
{
name: "error without PII information",
err: errors.New("this is an error string"),
hasPII: false,
},
{
name: "error with PII information",
err: fmt.Errorf("cannot access the preference file '/home/%s/.odo/preference.yaml'", user.Username),
hasPII: true,
},
}
for _, tt := range tests {
got := SetError(tt.err)
// if error has PII, string returned by SetError must not be the same as the error since it was sanitized
// else it will be the same.
if (tt.hasPII && got == tt.err.Error()) || (!tt.hasPII && got != tt.err.Error()) {
if tt.hasPII && strings.Contains(got, user.Username) {
t.Error("PII was not sanitized properly.")
} else {
t.Errorf("got: %s, want: %s", got, tt.err.Error())
}
}
}
}
// createConfigDir creates a mock filesystem
func createConfigDir(t *testing.T) string {
fs := filesystem.NewFakeFs()
configDir, err := fs.TempDir(os.TempDir(), "telemetry")
if err != nil {
t.Error(err)
}
return configDir
}

View File

@@ -13,6 +13,8 @@ import (
"strings"
"time"
"github.com/openshift/odo/pkg/preference"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
@@ -294,7 +296,10 @@ func CommonBeforeEach() CommonVar {
commonVar.Project = commonVar.CliRunner.CreateRandNamespaceProject()
commonVar.OriginalWorkingDirectory = Getwd()
os.Setenv("GLOBALODOCONFIG", filepath.Join(commonVar.Context, "preference.yaml"))
// Set ConsentTelemetry to false so that it does not prompt to set a preference value
cfg, _ := preference.New()
err := cfg.SetConfiguration(preference.ConsentTelemetrySetting, "false")
Expect(err).To(BeNil())
return commonVar
}

View File

@@ -10,6 +10,8 @@ import (
"github.com/tidwall/gjson"
)
const promtMessageSubString = "Help odo improve by allowing it to collect usage data."
var _ = Describe("odo preference and config command tests", func() {
// TODO: A neater way to provide odo path. Currently we assume \
// odo and oc in $PATH already.
@@ -53,6 +55,17 @@ var _ = Describe("odo preference and config command tests", func() {
})
Context("When viewing global config", func() {
var newContext string
// ConsentTelemetry is set to false in helper.CommonBeforeEach so that it does not prompt to set a value
// during the tests, but we want to check preference values as they would be in real time and hence
// we set the GLOBALODOCONFIG variable to a value in new context
var _ = JustBeforeEach(func() {
newContext = helper.CreateNewContext()
os.Setenv("GLOBALODOCONFIG", filepath.Join(newContext, "preference.yaml"))
})
var _ = JustAfterEach(func() {
helper.DeleteDir(newContext)
})
It("should get the default global config keys", func() {
configOutput := helper.CmdShouldPass("odo", "preference", "view")
preferences := []string{"UpdateNotification", "NamePrefix", "Timeout", "PushTarget", "BuildTimeout", "PushTimeout", "Experimental", "Ephemeral", "ConsentTelemetry"}
@@ -325,4 +338,44 @@ var _ = Describe("odo preference and config command tests", func() {
Expect(ok).To(BeFalse())
})
})
Context("When no ConsentTelemetry preference value is set", func() {
var _ = JustBeforeEach(func() {
// unset the preference in case it is already set
helper.CmdShouldPass("odo", "preference", "unset", "ConsentTelemetry", "-f")
})
It("prompt should not appear when user calls for help", func() {
output := helper.CmdShouldPass("odo", "create", "--help")
Expect(output).ToNot(ContainSubstring(promtMessageSubString))
})
It("prompt should not appear when preference command is run", func() {
output := helper.CmdShouldPass("odo", "preference", "view")
Expect(output).ToNot(ContainSubstring(promtMessageSubString))
output = helper.CmdShouldPass("odo", "preference", "set", "buildtimeout", "5", "-f")
Expect(output).ToNot(ContainSubstring(promtMessageSubString))
output = helper.CmdShouldPass("odo", "preference", "unset", "buildtimeout", "-f")
Expect(output).ToNot(ContainSubstring(promtMessageSubString))
})
It("prompt should appear when non-preference command is run", func() {
output := helper.CmdShouldPass("odo", "create", "nodejs", "--context", commonVar.Context)
Expect(output).To(ContainSubstring(promtMessageSubString))
})
})
Context("Prompt should not appear when", func() {
It("ConsentTelemetry is set to true", func() {
helper.CmdShouldPass("odo", "preference", "set", "ConsentTelemetry", "true", "-f")
output := helper.CmdShouldPass("odo", "create", "nodejs", "--context", commonVar.Context)
Expect(output).ToNot(ContainSubstring(promtMessageSubString))
})
It("ConsentTelemetry is set to false", func() {
helper.CmdShouldPass("odo", "preference", "set", "ConsentTelemetry", "false", "-f")
output := helper.CmdShouldPass("odo", "create", "nodejs", "--context", commonVar.Context)
Expect(output).ToNot(ContainSubstring(promtMessageSubString))
})
})
})

View File

@@ -1,40 +1,29 @@
package devfile
import (
"os"
"path/filepath"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/openshift/odo/tests/helper"
"os"
"path/filepath"
"time"
)
var _ = Describe("odo devfile app command tests", func() {
var namespace, context, currentWorkingDirectory, originalKubeconfig string
// Using program command according to cliRunner in devfile
cliRunner := helper.GetCliRunner()
var namespace string
var commonVar helper.CommonVar
// This is run before every Spec (It)
var _ = BeforeEach(func() {
SetDefaultEventuallyTimeout(10 * time.Minute)
context = helper.CreateNewContext()
os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "config.yaml"))
originalKubeconfig = os.Getenv("KUBECONFIG")
helper.LocalKubeconfigSet(context)
namespace = cliRunner.CreateRandNamespaceProject()
currentWorkingDirectory = helper.Getwd()
commonVar = helper.CommonBeforeEach()
helper.Chdir(commonVar.Context)
namespace = commonVar.Project
})
// This is run after every Spec (It)
var _ = AfterEach(func() {
cliRunner.DeleteNamespaceProject(namespace)
helper.Chdir(currentWorkingDirectory)
err := os.Setenv("KUBECONFIG", originalKubeconfig)
Expect(err).NotTo(HaveOccurred())
helper.DeleteDir(context)
os.Unsetenv("GLOBALODOCONFIG")
helper.CommonAfterEach(commonVar)
})
Context("listing apps", func() {

View File

@@ -129,7 +129,7 @@ var _ = Describe("odo devfile create command tests", func() {
It("should successfully create the devfile component and show json output for non connected cluster", func() {
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(devfilePath))
cmd := helper.Cmd("odo", "create", "nodejs", "--context", newContext, "-o", "json")
output := cmd.WithEnv("KUBECONFIG=/no/such/path").ShouldPass().Out()
output := cmd.WithEnv("KUBECONFIG=/no/such/path", "GLOBALODOCONFIG="+os.Getenv("GLOBALODOCONFIG")).ShouldPass().Out()
values := gjson.GetMany(output, "kind", "metadata.name", "status.state")
Expect(helper.GjsonMatcher(values, []string{"Component", "nodejs", "Unknown"})).To(Equal(true))
})
@@ -153,7 +153,7 @@ var _ = Describe("odo devfile create command tests", func() {
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"), filepath.Join(devfilePath))
cmd := helper.Cmd("odo", "create", "nodejs", "--context", newContext, "-o", "json")
output := cmd.WithEnv("KUBECONFIG=" + newKubeConfigPath).ShouldPass().Out()
output := cmd.WithEnv("KUBECONFIG="+newKubeConfigPath, "GLOBALODOCONFIG="+os.Getenv("GLOBALODOCONFIG")).ShouldPass().Out()
values := gjson.GetMany(output, "kind", "metadata.name", "status.state")
Expect(helper.GjsonMatcher(values, []string{"Component", "nodejs", "Unknown"})).To(Equal(true))

View File

@@ -98,7 +98,7 @@ var _ = Describe("odo devfile describe command tests", func() {
It("should show json output for non connected cluster", func() {
helper.CmdShouldPass("odo", "create", "nodejs", "--context", commonVar.Context)
output := helper.Cmd("odo", "describe", "--context", commonVar.Context, "-o", "json").WithEnv("KUBECONFIG=/no/path").ShouldPass().Out()
output := helper.Cmd("odo", "describe", "--context", commonVar.Context, "-o", "json").WithEnv("KUBECONFIG=/no/path", "GLOBALODOCONFIG="+os.Getenv("GLOBALODOCONFIG")).ShouldPass().Out()
values := gjson.GetMany(output, "kind", "metadata.name", "status.state")
Expect(helper.GjsonMatcher(values, []string{"Component", "nodejs", "Unknown"})).To(Equal(true))
})

View File

@@ -17,34 +17,25 @@ import (
)
var _ = Describe("odo devfile status command tests", func() {
var namespace, context, cmpName, currentWorkingDirectory, originalKubeconfig string
var namespace, context, cmpName string
var commonVar helper.CommonVar
// Using program commmand according to cliRunner in devfile
cliRunner := helper.GetCliRunner()
// This is run after every Spec (It)
var _ = BeforeEach(func() {
commonVar = helper.CommonBeforeEach()
SetDefaultEventuallyTimeout(5 * time.Minute)
context = helper.CreateNewContext()
os.Setenv("GLOBALODOCONFIG", filepath.Join(context, "config.yaml"))
originalKubeconfig = os.Getenv("KUBECONFIG")
helper.LocalKubeconfigSet(context)
namespace = cliRunner.CreateRandNamespaceProject()
currentWorkingDirectory = helper.Getwd()
cmpName = helper.RandString(6)
helper.Chdir(context)
namespace = commonVar.Project
context = commonVar.Context
helper.Chdir(commonVar.Context)
})
// Clean up after the test
// This is run after every Spec (It)
var _ = AfterEach(func() {
cliRunner.DeleteNamespaceProject(namespace)
helper.Chdir(currentWorkingDirectory)
err := os.Setenv("KUBECONFIG", originalKubeconfig)
Expect(err).NotTo(HaveOccurred())
helper.DeleteDir(context)
os.Unsetenv("GLOBALODOCONFIG")
helper.CommonAfterEach(commonVar)
})
Context("Verify devfile status works", func() {

View File

@@ -7,7 +7,6 @@ import (
"path/filepath"
"regexp"
"strings"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -16,17 +15,20 @@ import (
var _ = Describe("odo service command tests for OperatorHub", func() {
var commonVar helper.CommonVar
var project string
var oc helper.OcRunner
BeforeEach(func() {
SetDefaultEventuallyTimeout(10 * time.Minute)
SetDefaultConsistentlyDuration(30 * time.Second)
// TODO: remove this when OperatorHub integration is fully baked into odo
// helper.CmdShouldPass("odo", "preference", "set", "Experimental", "true")
commonVar = helper.CommonBeforeEach()
helper.Chdir(commonVar.Context)
oc = helper.NewOcRunner("oc")
})
AfterEach(func() {
helper.CommonAfterEach(commonVar)
})
preSetup := func() {
project = helper.CreateRandProject()
helper.CmdShouldPass("odo", "project", "set", project)

9
vendor/github.com/google/uuid/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,9 @@
language: go
go:
- 1.4.3
- 1.5.3
- tip
script:
- go test -v ./...

10
vendor/github.com/google/uuid/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# How to contribute
We definitely welcome patches and contribution to this project!
### Legal requirements
In order to protect both you and ourselves, you will need to sign the
[Contributor License Agreement](https://cla.developers.google.com/clas).
You may have already signed it for other Google projects.

9
vendor/github.com/google/uuid/CONTRIBUTORS generated vendored Normal file
View File

@@ -0,0 +1,9 @@
Paul Borman <borman@google.com>
bmatsuo
shawnps
theory
jboverfelt
dsymonds
cd1
wallclockbuilder
dansouza

27
vendor/github.com/google/uuid/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2009,2014 Google Inc. 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.

19
vendor/github.com/google/uuid/README.md generated vendored Normal file
View File

@@ -0,0 +1,19 @@
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
The uuid package generates and inspects UUIDs based on
[RFC 4122](http://tools.ietf.org/html/rfc4122)
and DCE 1.1: Authentication and Security Services.
This package is based on the github.com/pborman/uuid package (previously named
code.google.com/p/go-uuid). It differs from these earlier packages in that
a UUID is a 16 byte array rather than a byte slice. One loss due to this
change is the ability to represent an invalid UUID (vs a NIL UUID).
###### Install
`go get github.com/google/uuid`
###### Documentation
[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
Full `go doc` style documentation for the package can be viewed online without
installing this package by using the GoDoc site here:
http://godoc.org/github.com/google/uuid

80
vendor/github.com/google/uuid/dce.go generated vendored Normal file
View File

@@ -0,0 +1,80 @@
// Copyright 2016 Google Inc. 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
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
uuid, err := NewUUID()
if err == nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid, err
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCESecurity(Person, uint32(os.Getuid()))
func NewDCEPerson() (UUID, error) {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCESecurity(Group, uint32(os.Getgid()))
func NewDCEGroup() (UUID, error) {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID. Domains are only defined
// for Version 2 UUIDs.
func (uuid UUID) Domain() Domain {
return Domain(uuid[9])
}
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
// UUIDs.
func (uuid UUID) ID() uint32 {
return binary.BigEndian.Uint32(uuid[0:4])
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}

12
vendor/github.com/google/uuid/doc.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2016 Google Inc. 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 generates and inspects UUIDs.
//
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
// Services.
//
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
// maps or compared directly.
package uuid

1
vendor/github.com/google/uuid/go.mod generated vendored Normal file
View File

@@ -0,0 +1 @@
module github.com/google/uuid

53
vendor/github.com/google/uuid/hash.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
// Copyright 2016 Google Inc. 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
import (
"crypto/md5"
"crypto/sha1"
"hash"
)
// Well known namespace IDs and UUIDs
var (
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
Nil UUID // empty UUID, all zeros
)
// NewHash returns a new UUID derived from the hash of space concatenated with
// data generated by h. The hash should be at least 16 byte in length. The
// first 16 bytes of the hash are used to form the UUID. The version of the
// UUID will be the lower 4 bits of version. NewHash is used to implement
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
h.Write(space[:])
h.Write(data)
s := h.Sum(nil)
var uuid UUID
copy(uuid[:], s)
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
return uuid
}
// NewMD5 returns a new MD5 (Version 3) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(md5.New(), space, data, 3)
func NewMD5(space UUID, data []byte) UUID {
return NewHash(md5.New(), space, data, 3)
}
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(sha1.New(), space, data, 5)
func NewSHA1(space UUID, data []byte) UUID {
return NewHash(sha1.New(), space, data, 5)
}

37
vendor/github.com/google/uuid/marshal.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
// Copyright 2016 Google Inc. 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
import "fmt"
// MarshalText implements encoding.TextMarshaler.
func (uuid UUID) MarshalText() ([]byte, error) {
var js [36]byte
encodeHex(js[:], uuid)
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (uuid *UUID) UnmarshalText(data []byte) error {
id, err := ParseBytes(data)
if err == nil {
*uuid = id
}
return err
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (uuid UUID) MarshalBinary() ([]byte, error) {
return uuid[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (uuid *UUID) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(uuid[:], data)
return nil
}

90
vendor/github.com/google/uuid/node.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
// Copyright 2016 Google Inc. 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
import (
"sync"
)
var (
nodeMu sync.Mutex
ifname string // name of interface being used
nodeID [6]byte // hardware for version 1 UUIDs
zeroID [6]byte // nodeID with only 0's
)
// NodeInterface returns the name of the interface from which the NodeID was
// derived. The interface "user" is returned if the NodeID was set by
// SetNodeID.
func NodeInterface() string {
defer nodeMu.Unlock()
nodeMu.Lock()
return ifname
}
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
// If name is "" then the first usable interface found will be used or a random
// Node ID will be generated. If a named interface cannot be found then false
// is returned.
//
// SetNodeInterface never fails when name is "".
func SetNodeInterface(name string) bool {
defer nodeMu.Unlock()
nodeMu.Lock()
return setNodeInterface(name)
}
func setNodeInterface(name string) bool {
iname, addr := getHardwareInterface(name) // null implementation for js
if iname != "" && addr != nil {
ifname = iname
copy(nodeID[:], addr)
return true
}
// We found no interfaces with a valid hardware address. If name
// does not specify a specific interface generate a random Node ID
// (section 4.1.6)
if name == "" {
ifname = "random"
randomBits(nodeID[:])
return true
}
return false
}
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
// if not already set.
func NodeID() []byte {
defer nodeMu.Unlock()
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nid := nodeID
return nid[:]
}
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
// of id are used. If id is less than 6 bytes then false is returned and the
// Node ID is not set.
func SetNodeID(id []byte) bool {
if len(id) < 6 {
return false
}
defer nodeMu.Unlock()
nodeMu.Lock()
copy(nodeID[:], id)
ifname = "user"
return true
}
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte {
var node [6]byte
copy(node[:], uuid[10:])
return node[:]
}

12
vendor/github.com/google/uuid/node_js.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build js
package uuid
// getHardwareInterface returns nil values for the JS version of the code.
// This remvoves the "net" dependency, because it is not used in the browser.
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
func getHardwareInterface(name string) (string, []byte) { return "", nil }

33
vendor/github.com/google/uuid/node_net.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
// Copyright 2017 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !js
package uuid
import "net"
var interfaces []net.Interface // cached list of interfaces
// getHardwareInterface returns the name and hardware address of interface name.
// If name is "" then the name and hardware address of one of the system's
// interfaces is returned. If no interfaces are found (name does not exist or
// there are no interfaces) then "", nil is returned.
//
// Only addresses of at least 6 bytes are returned.
func getHardwareInterface(name string) (string, []byte) {
if interfaces == nil {
var err error
interfaces, err = net.Interfaces()
if err != nil {
return "", nil
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
return ifs.Name, ifs.HardwareAddr
}
}
return "", nil
}

59
vendor/github.com/google/uuid/sql.go generated vendored Normal file
View File

@@ -0,0 +1,59 @@
// Copyright 2016 Google Inc. 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
import (
"database/sql/driver"
"fmt"
)
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
// Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error {
switch src := src.(type) {
case nil:
return nil
case string:
// if an empty UUID comes from a table, we return a null UUID
if src == "" {
return nil
}
// see Parse for required string format
u, err := Parse(src)
if err != nil {
return fmt.Errorf("Scan: %v", err)
}
*uuid = u
case []byte:
// if an empty UUID comes from a table, we return a null UUID
if len(src) == 0 {
return nil
}
// assumes a simple slice of bytes if 16 bytes
// otherwise attempts to parse
if len(src) != 16 {
return uuid.Scan(string(src))
}
copy((*uuid)[:], src)
default:
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
}
return nil
}
// Value implements sql.Valuer so that UUIDs can be written to databases
// transparently. Currently, UUIDs map to strings. Please consult
// database-specific driver documentation for matching types.
func (uuid UUID) Value() (driver.Value, error) {
return uuid.String(), nil
}

123
vendor/github.com/google/uuid/time.go generated vendored Normal file
View File

@@ -0,0 +1,123 @@
// Copyright 2016 Google Inc. 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
import (
"encoding/binary"
"sync"
"time"
)
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
// 1582.
type Time int64
const (
lillian = 2299160 // Julian day of 15 Oct 1582
unix = 2440587 // Julian day of 1 Jan 1970
epoch = unix - lillian // Days between epochs
g1582 = epoch * 86400 // seconds between epochs
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
)
var (
timeMu sync.Mutex
lasttime uint64 // last time we returned
clockSeq uint16 // clock sequence for this run
timeNow = time.Now // for testing
)
// UnixTime converts t the number of seconds and nanoseconds using the Unix
// epoch of 1 Jan 1970.
func (t Time) UnixTime() (sec, nsec int64) {
sec = int64(t - g1582ns100)
nsec = (sec % 10000000) * 100
sec /= 10000000
return sec, nsec
}
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
// clock sequence as well as adjusting the clock sequence as needed. An error
// is returned if the current time cannot be determined.
func GetTime() (Time, uint16, error) {
defer timeMu.Unlock()
timeMu.Lock()
return getTime()
}
func getTime() (Time, uint16, error) {
t := timeNow()
// If we don't have a clock sequence already, set one.
if clockSeq == 0 {
setClockSequence(-1)
}
now := uint64(t.UnixNano()/100) + g1582ns100
// If time has gone backwards with this clock sequence then we
// increment the clock sequence
if now <= lasttime {
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
}
lasttime = now
return Time(now), clockSeq, nil
}
// ClockSequence returns the current clock sequence, generating one if not
// already set. The clock sequence is only used for Version 1 UUIDs.
//
// The uuid package does not use global static storage for the clock sequence or
// the last time a UUID was generated. Unless SetClockSequence is used, a new
// random clock sequence is generated the first time a clock sequence is
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
func ClockSequence() int {
defer timeMu.Unlock()
timeMu.Lock()
return clockSequence()
}
func clockSequence() int {
if clockSeq == 0 {
setClockSequence(-1)
}
return int(clockSeq & 0x3fff)
}
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated.
func SetClockSequence(seq int) {
defer timeMu.Unlock()
timeMu.Lock()
setClockSequence(seq)
}
func setClockSequence(seq int) {
if seq == -1 {
var b [2]byte
randomBits(b[:]) // clock sequence
seq = int(b[0])<<8 | int(b[1])
}
oldSeq := clockSeq
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
if oldSeq != clockSeq {
lasttime = 0
}
}
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. The time is only defined for version 1 and 2 UUIDs.
func (uuid UUID) Time() Time {
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
return Time(time)
}
// ClockSequence returns the clock sequence encoded in uuid.
// The clock sequence is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) ClockSequence() int {
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
}

43
vendor/github.com/google/uuid/util.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2016 Google Inc. 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
import (
"io"
)
// randomBits completely fills slice b with random data.
func randomBits(b []byte) {
if _, err := io.ReadFull(rander, b); err != nil {
panic(err.Error()) // rand should never fail
}
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts hex characters x1 and x2 into a byte.
func xtob(x1, x2 byte) (byte, bool) {
b1 := xvalues[x1]
b2 := xvalues[x2]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

245
vendor/github.com/google/uuid/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,245 @@
// Copyright 2018 Google Inc. 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
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
)
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type UUID [16]byte
// A Version represents a UUID's version.
type Version byte
// A Variant represents a UUID's variant.
type Variant byte
// Constants returned by Variant.
const (
Invalid = Variant(iota) // Invalid UUID
RFC4122 // The variant specified in RFC4122
Reserved // Reserved, NCS backward compatibility.
Microsoft // Reserved, Microsoft Corporation backward compatibility.
Future // Reserved for future definition.
)
var rander = rand.Reader // random function
// Parse decodes s into a UUID or returns an error. Both the standard UUID
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
func Parse(s string) (UUID, error) {
var uuid UUID
switch len(s) {
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36:
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9:
if strings.ToLower(s[:9]) != "urn:uuid:" {
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
}
s = s[9:]
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
case 36 + 2:
s = s[1:]
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
case 32:
var ok bool
for i := range uuid {
uuid[i], ok = xtob(s[i*2], s[i*2+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
v, ok := xtob(s[x], s[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
uuid[i] = v
}
return uuid, nil
}
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
func ParseBytes(b []byte) (UUID, error) {
var uuid UUID
switch len(b) {
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
}
b = b[9:]
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
b = b[1:]
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
var ok bool
for i := 0; i < 32; i += 2 {
uuid[i/2], ok = xtob(b[i], b[i+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
}
return uuid, nil
default:
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
}
// s is now at least 36 bytes long
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
v, ok := xtob(b[x], b[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
uuid[i] = v
}
return uuid, nil
}
// MustParse is like Parse but panics if the string cannot be parsed.
// It simplifies safe initialization of global variables holding compiled UUIDs.
func MustParse(s string) UUID {
uuid, err := Parse(s)
if err != nil {
panic(`uuid: Parse(` + s + `): ` + err.Error())
}
return uuid
}
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
// does not have a length of 16. The bytes are copied from the slice.
func FromBytes(b []byte) (uuid UUID, err error) {
err = uuid.UnmarshalBinary(b)
return uuid, err
}
// Must returns uuid if err is nil and panics otherwise.
func Must(uuid UUID, err error) UUID {
if err != nil {
panic(err)
}
return uuid
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
var buf [36]byte
encodeHex(buf[:], uuid)
return string(buf[:])
}
// URN returns the RFC 2141 URN form of uuid,
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
func (uuid UUID) URN() string {
var buf [36 + 9]byte
copy(buf[:], "urn:uuid:")
encodeHex(buf[9:], uuid)
return string(buf[:])
}
func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst, uuid[:4])
dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-'
hex.Encode(dst[14:18], uuid[6:8])
dst[18] = '-'
hex.Encode(dst[19:23], uuid[8:10])
dst[23] = '-'
hex.Encode(dst[24:], uuid[10:])
}
// Variant returns the variant encoded in uuid.
func (uuid UUID) Variant() Variant {
switch {
case (uuid[8] & 0xc0) == 0x80:
return RFC4122
case (uuid[8] & 0xe0) == 0xc0:
return Microsoft
case (uuid[8] & 0xe0) == 0xe0:
return Future
default:
return Reserved
}
}
// Version returns the version of uuid.
func (uuid UUID) Version() Version {
return Version(uuid[6] >> 4)
}
func (v Version) String() string {
if v > 15 {
return fmt.Sprintf("BAD_VERSION_%d", v)
}
return fmt.Sprintf("VERSION_%d", v)
}
func (v Variant) String() string {
switch v {
case RFC4122:
return "RFC4122"
case Reserved:
return "Reserved"
case Microsoft:
return "Microsoft"
case Future:
return "Future"
case Invalid:
return "Invalid"
}
return fmt.Sprintf("BadVariant%d", int(v))
}
// SetRand sets the random number generator to r, which implements io.Reader.
// If r.Read returns an error when the package requests random data then
// a panic will be issued.
//
// Calling SetRand with nil sets the random number generator to the default
// generator.
func SetRand(r io.Reader) {
if r == nil {
rander = rand.Reader
return
}
rander = r
}

44
vendor/github.com/google/uuid/version1.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2016 Google Inc. 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
import (
"encoding/binary"
)
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil and an error.
//
// In most cases, New should be used.
func NewUUID() (UUID, error) {
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nodeMu.Unlock()
var uuid UUID
now, seq, err := GetTime()
if err != nil {
return uuid, err
}
timeLow := uint32(now & 0xffffffff)
timeMid := uint16((now >> 32) & 0xffff)
timeHi := uint16((now >> 48) & 0x0fff)
timeHi |= 0x1000 // Version 1
binary.BigEndian.PutUint32(uuid[0:], timeLow)
binary.BigEndian.PutUint16(uuid[4:], timeMid)
binary.BigEndian.PutUint16(uuid[6:], timeHi)
binary.BigEndian.PutUint16(uuid[8:], seq)
copy(uuid[10:], nodeID[:])
return uuid, nil
}

38
vendor/github.com/google/uuid/version4.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2016 Google Inc. 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
import "io"
// New creates a new random UUID or panics. New is equivalent to
// the expression
//
// uuid.Must(uuid.NewRandom())
func New() UUID {
return Must(NewRandom())
}
// NewRandom returns a Random (Version 4) UUID.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
var uuid UUID
_, err := io.ReadFull(rander, uuid[:])
if err != nil {
return Nil, err
}
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}

View File

@@ -1,2 +0,0 @@
_*
cover*.out

View File

@@ -1,20 +0,0 @@
language: go
go:
- "1.x"
- master
matrix:
allow_failures:
- go: master
fast_finish: true
before_script:
- go get -t -v ./...
- go get -v golang.org/x/lint/golint
- go get -v honnef.co/go/tools/cmd/staticcheck
- go vet ./...
script:
# run all our tests with race detector
- go test -v -race ./...
# "go vet on steroids" + linter
- staticcheck ./...

View File

@@ -1,373 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@@ -1,62 +0,0 @@
slug
====
Package `slug` generate slug from unicode string, URL-friendly slugify with
multiple languages support.
[![GoDoc](https://godoc.org/github.com/gosimple/slug?status.png)](https://godoc.org/github.com/gosimple/slug)
[![Build Status](https://travis-ci.com/gosimple/slug.svg?branch=master)](https://travis-ci.com/gosimple/slug)
[Documentation online](http://godoc.org/github.com/gosimple/slug)
## Example
```go
package main
import (
"fmt"
"github.com/gosimple/slug"
)
func main() {
text := slug.Make("Hellö Wörld хелло ворлд")
fmt.Println(text) // Will print: "hello-world-khello-vorld"
someText := slug.Make("影師")
fmt.Println(someText) // Will print: "ying-shi"
enText := slug.MakeLang("This & that", "en")
fmt.Println(enText) // Will print: "this-and-that"
deText := slug.MakeLang("Diese & Dass", "de")
fmt.Println(deText) // Will print: "diese-und-dass"
slug.Lowercase = false // Keep uppercase characters
deUppercaseText := slug.MakeLang("Diese & Dass", "de")
fmt.Println(deUppercaseText) // Will print: "Diese-und-Dass"
slug.CustomSub = map[string]string{
"water": "sand",
}
textSub := slug.Make("water is hot")
fmt.Println(textSub) // Will print: "sand-is-hot"
}
```
### Requests or bugs?
<https://github.com/gosimple/slug/issues>
## Installation
```sh
go get -u github.com/gosimple/slug
```
## License
The source files are distributed under the
[Mozilla Public License, version 2.0](http://mozilla.org/MPL/2.0/),
unless otherwise noted.
Please read the [FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html)
if you have further questions regarding the license.

View File

@@ -1,47 +0,0 @@
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/*
Package slug generate slug from unicode string, URL-friendly slugify with
multiple languages support.
Example:
package main
import(
"github.com/gosimple/slug"
"fmt"
)
func main () {
text := slug.Make("Hellö Wörld хелло ворлд")
fmt.Println(text) // Will print: "hello-world-khello-vorld"
someText := slug.Make("影師")
fmt.Println(someText) // Will print: "ying-shi"
enText := slug.MakeLang("This & that", "en")
fmt.Println(enText) // Will print: "this-and-that"
deText := slug.MakeLang("Diese & Dass", "de")
fmt.Println(deText) // Will print: "diese-und-dass"
slug.Lowercase = false // Keep uppercase characters
deUppercaseText := slug.MakeLang("Diese & Dass", "de")
fmt.Println(deUppercaseText) // Will print: "Diese-und-Dass"
slug.CustomSub = map[string]string{
"water": "sand",
}
textSub := slug.Make("water is hot")
fmt.Println(textSub) // Will print: "sand-is-hot"
}
Requests or bugs?
https://github.com/gosimple/slug/issues
*/
package slug

View File

@@ -1,5 +0,0 @@
module github.com/gosimple/slug
go 1.13
require github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be

View File

@@ -1,2 +0,0 @@
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=

View File

@@ -1,120 +0,0 @@
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package slug
func init() {
// Merge language subs with the default one.
// TODO: Find better way so all langs are merged automatically and better
// tested.
for _, sub := range []*map[rune]string{
&deSub, &enSub, &esSub, &fiSub, &grSub, &kkSub, &nlSub, &plSub, &svSub, &trSub,
} {
for key, value := range defaultSub {
(*sub)[key] = value
}
}
}
var defaultSub = map[rune]string{
'"': "",
'\'': "",
'': "",
'': "-", // figure dash
'': "-", // en dash
'—': "-", // em dash
'―': "-", // horizontal bar
}
var deSub = map[rune]string{
'&': "und",
'@': "an",
'ä': "ae",
'Ä': "Ae",
'ö': "oe",
'Ö': "Oe",
'ü': "ue",
'Ü': "Ue",
}
var enSub = map[rune]string{
'&': "and",
'@': "at",
}
var esSub = map[rune]string{
'&': "y",
'@': "en",
}
var fiSub = map[rune]string{
'&': "ja",
'@': "at",
}
var grSub = map[rune]string{
'&': "kai",
'η': "i",
'ή': "i",
'Η': "i",
'ι': "i",
'ί': "i",
'ϊ': "i",
'Ι': "i",
'χ': "x",
'Χ': "x",
'ω': "w",
'ώ': "w",
'Ω': "w",
'ϋ': "u",
}
var kkSub = map[rune]string{
'&': "jane",
'ә': "a",
'ғ': "g",
'қ': "q",
'ң': "n",
'ө': "o",
'ұ': "u",
'Ә': "A",
'Ғ': "G",
'Қ': "Q",
'Ң': "N",
'Ө': "O",
'Ұ': "U",
}
var nlSub = map[rune]string{
'&': "en",
'@': "at",
}
var plSub = map[rune]string{
'&': "i",
'@': "na",
}
var svSub = map[rune]string{
'&': "och",
'@': "snabel a",
}
var trSub = map[rune]string{
'&': "ve",
'@': "et",
'ş': "s",
'Ş': "S",
'ü': "u",
'Ü': "U",
'ö': "o",
'Ö': "O",
'İ': "I",
'ı': "i",
'ğ': "g",
'Ğ': "G",
'ç': "c",
'Ç': "C",
}

View File

@@ -1,174 +0,0 @@
// Copyright 2013 by Dobrosław Żybort. All rights reserved.
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package slug
import (
"bytes"
"regexp"
"sort"
"strings"
"github.com/rainycape/unidecode"
)
var (
// CustomSub stores custom substitution map
CustomSub map[string]string
// CustomRuneSub stores custom rune substitution map
CustomRuneSub map[rune]string
// MaxLength stores maximum slug length.
// It's smart so it will cat slug after full word.
// By default slugs aren't shortened.
// If MaxLength is smaller than length of the first word, then returned
// slug will contain only substring from the first word truncated
// after MaxLength.
MaxLength int
// Lowercase defines if the resulting slug is transformed to lowercase.
// Default is true.
Lowercase = true
regexpNonAuthorizedChars = regexp.MustCompile("[^a-zA-Z0-9-_]")
regexpMultipleDashes = regexp.MustCompile("-+")
)
//=============================================================================
// Make returns slug generated from provided string. Will use "en" as language
// substitution.
func Make(s string) (slug string) {
return MakeLang(s, "en")
}
// MakeLang returns slug generated from provided string and will use provided
// language for chars substitution.
func MakeLang(s string, lang string) (slug string) {
slug = strings.TrimSpace(s)
// Custom substitutions
// Always substitute runes first
slug = SubstituteRune(slug, CustomRuneSub)
slug = Substitute(slug, CustomSub)
// Process string with selected substitution language.
// Catch ISO 3166-1, ISO 639-1:2002 and ISO 639-3:2007.
switch strings.ToLower(lang) {
case "de", "deu":
slug = SubstituteRune(slug, deSub)
case "en", "eng":
slug = SubstituteRune(slug, enSub)
case "es", "spa":
slug = SubstituteRune(slug, esSub)
case "fi", "fin":
slug = SubstituteRune(slug, fiSub)
case "gr", "el", "ell":
slug = SubstituteRune(slug, grSub)
case "kz", "kk", "kaz":
slug = SubstituteRune(slug, kkSub)
case "nl", "nld":
slug = SubstituteRune(slug, nlSub)
case "pl", "pol":
slug = SubstituteRune(slug, plSub)
case "sv", "swe":
slug = SubstituteRune(slug, svSub)
case "tr", "tur":
slug = SubstituteRune(slug, trSub)
default: // fallback to "en" if lang not found
slug = SubstituteRune(slug, enSub)
}
// Process all non ASCII symbols
slug = unidecode.Unidecode(slug)
if Lowercase {
slug = strings.ToLower(slug)
}
// Process all remaining symbols
slug = regexpNonAuthorizedChars.ReplaceAllString(slug, "-")
slug = regexpMultipleDashes.ReplaceAllString(slug, "-")
slug = strings.Trim(slug, "-_")
if MaxLength > 0 {
slug = smartTruncate(slug)
}
return slug
}
// Substitute returns string with superseded all substrings from
// provided substitution map. Substitution map will be applied in alphabetic
// order. Many passes, on one substitution another one could apply.
func Substitute(s string, sub map[string]string) (buf string) {
buf = s
var keys []string
for k := range sub {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
buf = strings.Replace(buf, key, sub[key], -1)
}
return
}
// SubstituteRune substitutes string chars with provided rune
// substitution map. One pass.
func SubstituteRune(s string, sub map[rune]string) string {
var buf bytes.Buffer
for _, c := range s {
if d, ok := sub[c]; ok {
buf.WriteString(d)
} else {
buf.WriteRune(c)
}
}
return buf.String()
}
func smartTruncate(text string) string {
if len(text) < MaxLength {
return text
}
var truncated string
words := strings.SplitAfter(text, "-")
// If MaxLength is smaller than length of the first word return word
// truncated after MaxLength.
if len(words[0]) > MaxLength {
return words[0][:MaxLength]
}
for _, word := range words {
if len(truncated)+len(word)-1 <= MaxLength {
truncated = truncated + word
} else {
break
}
}
return strings.Trim(truncated, "-")
}
// IsSlug returns True if provided text does not contain white characters,
// punctuation, all letters are lower case and only from ASCII range.
// It could contain `-` and `_` but not at the beginning or end of the text.
// It should be in range of the MaxLength var if specified.
// All output from slug.Make(text) should pass this test.
func IsSlug(text string) bool {
if text == "" ||
(MaxLength > 0 && len(text) > MaxLength) ||
text[0] == '-' || text[0] == '_' ||
text[len(text)-1] == '-' || text[len(text)-1] == '_' {
return false
}
for _, c := range text {
if (c < 'a' || c > 'z') && c != '-' && c != '_' && (c < '0' || c > '9') {
return false
}
}
return true
}

10
vendor/github.com/pborman/uuid/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,10 @@
language: go
go:
- "1.9"
- "1.10"
- "1.11"
- tip
script:
- go test -v ./...

10
vendor/github.com/pborman/uuid/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# How to contribute
We definitely welcome patches and contribution to this project!
### Legal requirements
In order to protect both you and ourselves, you will need to sign the
[Contributor License Agreement](https://cla.developers.google.com/clas).
You may have already signed it for other Google projects.

1
vendor/github.com/pborman/uuid/CONTRIBUTORS generated vendored Normal file
View File

@@ -0,0 +1 @@
Paul Borman <borman@google.com>

27
vendor/github.com/pborman/uuid/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2009,2014 Google Inc. 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.

15
vendor/github.com/pborman/uuid/README.md generated vendored Normal file
View File

@@ -0,0 +1,15 @@
This project was automatically exported from code.google.com/p/go-uuid
# uuid ![build status](https://travis-ci.org/pborman/uuid.svg?branch=master)
The uuid package generates and inspects UUIDs based on [RFC 4122](http://tools.ietf.org/html/rfc4122) and DCE 1.1: Authentication and Security Services.
This package now leverages the github.com/google/uuid package (which is based off an earlier version of this package).
###### Install
`go get github.com/pborman/uuid`
###### Documentation
[![GoDoc](https://godoc.org/github.com/pborman/uuid?status.svg)](http://godoc.org/github.com/pborman/uuid)
Full `go doc` style documentation for the package can be viewed online without installing this package by using the GoDoc site here:
http://godoc.org/github.com/pborman/uuid

84
vendor/github.com/pborman/uuid/dce.go generated vendored Normal file
View File

@@ -0,0 +1,84 @@
// Copyright 2011 Google Inc. 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
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) UUID {
uuid := NewUUID()
if uuid != nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCEPerson(Person, uint32(os.Getuid()))
func NewDCEPerson() UUID {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCEGroup(Group, uint32(os.Getgid()))
func NewDCEGroup() UUID {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID or false.
func (uuid UUID) Domain() (Domain, bool) {
if v, _ := uuid.Version(); v != 2 {
return 0, false
}
return Domain(uuid[9]), true
}
// Id returns the id for a Version 2 UUID or false.
func (uuid UUID) Id() (uint32, bool) {
if v, _ := uuid.Version(); v != 2 {
return 0, false
}
return binary.BigEndian.Uint32(uuid[0:4]), true
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}

13
vendor/github.com/pborman/uuid/doc.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The uuid package generates and inspects UUIDs.
//
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
// Services.
//
// This package is a partial wrapper around the github.com/google/uuid package.
// This package represents a UUID as []byte while github.com/google/uuid
// represents a UUID as [16]byte.
package uuid

3
vendor/github.com/pborman/uuid/go.mod generated vendored Normal file
View File

@@ -0,0 +1,3 @@
module github.com/pborman/uuid
require github.com/google/uuid v1.0.0

2
vendor/github.com/pborman/uuid/go.sum generated vendored Normal file
View File

@@ -0,0 +1,2 @@
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

53
vendor/github.com/pborman/uuid/hash.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
// Copyright 2011 Google Inc. 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
import (
"crypto/md5"
"crypto/sha1"
"hash"
)
// Well known Name Space IDs and UUIDs
var (
NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
NIL = Parse("00000000-0000-0000-0000-000000000000")
)
// NewHash returns a new UUID derived from the hash of space concatenated with
// data generated by h. The hash should be at least 16 byte in length. The
// first 16 bytes of the hash are used to form the UUID. The version of the
// UUID will be the lower 4 bits of version. NewHash is used to implement
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
h.Write(space)
h.Write([]byte(data))
s := h.Sum(nil)
uuid := make([]byte, 16)
copy(uuid, s)
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
return uuid
}
// NewMD5 returns a new MD5 (Version 3) UUID based on the
// supplied name space and data.
//
// NewHash(md5.New(), space, data, 3)
func NewMD5(space UUID, data []byte) UUID {
return NewHash(md5.New(), space, data, 3)
}
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
// supplied name space and data.
//
// NewHash(sha1.New(), space, data, 5)
func NewSHA1(space UUID, data []byte) UUID {
return NewHash(sha1.New(), space, data, 5)
}

85
vendor/github.com/pborman/uuid/marshal.go generated vendored Normal file
View File

@@ -0,0 +1,85 @@
// Copyright 2016 Google Inc. 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
import (
"errors"
"fmt"
guuid "github.com/google/uuid"
)
// MarshalText implements encoding.TextMarshaler.
func (u UUID) MarshalText() ([]byte, error) {
if len(u) != 16 {
return nil, nil
}
var js [36]byte
encodeHex(js[:], u)
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (u *UUID) UnmarshalText(data []byte) error {
if len(data) == 0 {
return nil
}
id := Parse(string(data))
if id == nil {
return errors.New("invalid UUID")
}
*u = id
return nil
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (u UUID) MarshalBinary() ([]byte, error) {
return u[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (u *UUID) UnmarshalBinary(data []byte) error {
if len(data) == 0 {
return nil
}
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
var id [16]byte
copy(id[:], data)
*u = id[:]
return nil
}
// MarshalText implements encoding.TextMarshaler.
func (u Array) MarshalText() ([]byte, error) {
var js [36]byte
encodeHex(js[:], u[:])
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (u *Array) UnmarshalText(data []byte) error {
id, err := guuid.ParseBytes(data)
if err != nil {
return err
}
*u = Array(id)
return nil
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (u Array) MarshalBinary() ([]byte, error) {
return u[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (u *Array) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(u[:], data)
return nil
}

50
vendor/github.com/pborman/uuid/node.go generated vendored Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2011 Google Inc. 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
import (
guuid "github.com/google/uuid"
)
// NodeInterface returns the name of the interface from which the NodeID was
// derived. The interface "user" is returned if the NodeID was set by
// SetNodeID.
func NodeInterface() string {
return guuid.NodeInterface()
}
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
// If name is "" then the first usable interface found will be used or a random
// Node ID will be generated. If a named interface cannot be found then false
// is returned.
//
// SetNodeInterface never fails when name is "".
func SetNodeInterface(name string) bool {
return guuid.SetNodeInterface(name)
}
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
// if not already set.
func NodeID() []byte {
return guuid.NodeID()
}
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
// of id are used. If id is less than 6 bytes then false is returned and the
// Node ID is not set.
func SetNodeID(id []byte) bool {
return guuid.SetNodeID(id)
}
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte {
if len(uuid) != 16 {
return nil
}
node := make([]byte, 6)
copy(node, uuid[10:])
return node
}

68
vendor/github.com/pborman/uuid/sql.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
// Copyright 2015 Google Inc. 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
import (
"database/sql/driver"
"errors"
"fmt"
)
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
// Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error {
switch src.(type) {
case string:
// if an empty UUID comes from a table, we return a null UUID
if src.(string) == "" {
return nil
}
// see uuid.Parse for required string format
parsed := Parse(src.(string))
if parsed == nil {
return errors.New("Scan: invalid UUID format")
}
*uuid = parsed
case []byte:
b := src.([]byte)
// if an empty UUID comes from a table, we return a null UUID
if len(b) == 0 {
return nil
}
// assumes a simple slice of bytes if 16 bytes
// otherwise attempts to parse
if len(b) == 16 {
parsed := make([]byte, 16)
copy(parsed, b)
*uuid = UUID(parsed)
} else {
u := Parse(string(b))
if u == nil {
return errors.New("Scan: invalid UUID format")
}
*uuid = u
}
default:
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
}
return nil
}
// Value implements sql.Valuer so that UUIDs can be written to databases
// transparently. Currently, UUIDs map to strings. Please consult
// database-specific driver documentation for matching types.
func (uuid UUID) Value() (driver.Value, error) {
return uuid.String(), nil
}

57
vendor/github.com/pborman/uuid/time.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
// Copyright 2014 Google Inc. 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
import (
"encoding/binary"
guuid "github.com/google/uuid"
)
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
// 1582.
type Time = guuid.Time
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
// clock sequence as well as adjusting the clock sequence as needed. An error
// is returned if the current time cannot be determined.
func GetTime() (Time, uint16, error) { return guuid.GetTime() }
// ClockSequence returns the current clock sequence, generating one if not
// already set. The clock sequence is only used for Version 1 UUIDs.
//
// The uuid package does not use global static storage for the clock sequence or
// the last time a UUID was generated. Unless SetClockSequence a new random
// clock sequence is generated the first time a clock sequence is requested by
// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated
// for
func ClockSequence() int { return guuid.ClockSequence() }
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated.
func SetClockSequence(seq int) { guuid.SetClockSequence(seq) }
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. It returns false if uuid is not valid. The time is only well defined
// for version 1 and 2 UUIDs.
func (uuid UUID) Time() (Time, bool) {
if len(uuid) != 16 {
return 0, false
}
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
return Time(time), true
}
// ClockSequence returns the clock sequence encoded in uuid. It returns false
// if uuid is not valid. The clock sequence is only well defined for version 1
// and 2 UUIDs.
func (uuid UUID) ClockSequence() (int, bool) {
if len(uuid) != 16 {
return 0, false
}
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true
}

32
vendor/github.com/pborman/uuid/util.go generated vendored Normal file
View File

@@ -0,0 +1,32 @@
// Copyright 2011 Google Inc. 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
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts the the first two hex bytes of x into a byte.
func xtob(x string) (byte, bool) {
b1 := xvalues[x[0]]
b2 := xvalues[x[1]]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

162
vendor/github.com/pborman/uuid/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,162 @@
// Copyright 2011 Google Inc. 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
import (
"bytes"
"crypto/rand"
"encoding/hex"
"io"
guuid "github.com/google/uuid"
)
// Array is a pass-by-value UUID that can be used as an effecient key in a map.
type Array [16]byte
// UUID converts uuid into a slice.
func (uuid Array) UUID() UUID {
return uuid[:]
}
// String returns the string representation of uuid,
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
func (uuid Array) String() string {
return guuid.UUID(uuid).String()
}
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type UUID []byte
// A Version represents a UUIDs version.
type Version = guuid.Version
// A Variant represents a UUIDs variant.
type Variant = guuid.Variant
// Constants returned by Variant.
const (
Invalid = guuid.Invalid // Invalid UUID
RFC4122 = guuid.RFC4122 // The variant specified in RFC4122
Reserved = guuid.Reserved // Reserved, NCS backward compatibility.
Microsoft = guuid.Microsoft // Reserved, Microsoft Corporation backward compatibility.
Future = guuid.Future // Reserved for future definition.
)
var rander = rand.Reader // random function
// New returns a new random (version 4) UUID as a string. It is a convenience
// function for NewRandom().String().
func New() string {
return NewRandom().String()
}
// Parse decodes s into a UUID or returns nil. See github.com/google/uuid for
// the formats parsed.
func Parse(s string) UUID {
gu, err := guuid.Parse(s)
if err == nil {
return gu[:]
}
return nil
}
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
func ParseBytes(b []byte) (UUID, error) {
gu, err := guuid.ParseBytes(b)
if err == nil {
return gu[:], nil
}
return nil, err
}
// Equal returns true if uuid1 and uuid2 are equal.
func Equal(uuid1, uuid2 UUID) bool {
return bytes.Equal(uuid1, uuid2)
}
// Array returns an array representation of uuid that can be used as a map key.
// Array panics if uuid is not valid.
func (uuid UUID) Array() Array {
if len(uuid) != 16 {
panic("invalid uuid")
}
var a Array
copy(a[:], uuid)
return a
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
if len(uuid) != 16 {
return ""
}
var buf [36]byte
encodeHex(buf[:], uuid)
return string(buf[:])
}
// URN returns the RFC 2141 URN form of uuid,
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
func (uuid UUID) URN() string {
if len(uuid) != 16 {
return ""
}
var buf [36 + 9]byte
copy(buf[:], "urn:uuid:")
encodeHex(buf[9:], uuid)
return string(buf[:])
}
func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst[:], uuid[:4])
dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-'
hex.Encode(dst[14:18], uuid[6:8])
dst[18] = '-'
hex.Encode(dst[19:23], uuid[8:10])
dst[23] = '-'
hex.Encode(dst[24:], uuid[10:])
}
// Variant returns the variant encoded in uuid. It returns Invalid if
// uuid is invalid.
func (uuid UUID) Variant() Variant {
if len(uuid) != 16 {
return Invalid
}
switch {
case (uuid[8] & 0xc0) == 0x80:
return RFC4122
case (uuid[8] & 0xe0) == 0xc0:
return Microsoft
case (uuid[8] & 0xe0) == 0xe0:
return Future
default:
return Reserved
}
}
// Version returns the version of uuid. It returns false if uuid is not
// valid.
func (uuid UUID) Version() (Version, bool) {
if len(uuid) != 16 {
return 0, false
}
return Version(uuid[6] >> 4), true
}
// SetRand sets the random number generator to r, which implements io.Reader.
// If r.Read returns an error when the package requests random data then
// a panic will be issued.
//
// Calling SetRand with nil sets the random number generator to the default
// generator.
func SetRand(r io.Reader) {
guuid.SetRand(r)
}

23
vendor/github.com/pborman/uuid/version1.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
// Copyright 2011 Google Inc. 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
import (
guuid "github.com/google/uuid"
)
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil.
func NewUUID() UUID {
gu, err := guuid.NewUUID()
if err == nil {
return UUID(gu[:])
}
return nil
}

26
vendor/github.com/pborman/uuid/version4.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
// Copyright 2011 Google Inc. 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
import guuid "github.com/google/uuid"
// Random returns a Random (Version 4) UUID or panics.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() UUID {
if gu, err := guuid.NewRandom(); err == nil {
return UUID(gu[:])
}
return nil
}

View File

@@ -1,203 +0,0 @@
Copyright 2014 Rainy Cape S.L. <hello@rainycape.com>
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,6 +0,0 @@
unidecode
=========
Unicode transliterator in Golang - Replaces non-ASCII characters with their ASCII approximations.
[![GoDoc](https://godoc.org/github.com/rainycape/unidecode?status.svg)](https://godoc.org/github.com/rainycape/unidecode)

View File

@@ -1,41 +0,0 @@
package unidecode
import (
"compress/zlib"
"encoding/binary"
"io"
"strings"
)
var (
transliterations [65536][]rune
transCount = rune(len(transliterations))
getUint16 = binary.LittleEndian.Uint16
)
func decodeTransliterations() {
r, err := zlib.NewReader(strings.NewReader(tableData))
if err != nil {
panic(err)
}
defer r.Close()
tmp1 := make([]byte, 2)
tmp2 := tmp1[:1]
for {
if _, err := io.ReadAtLeast(r, tmp1, 2); err != nil {
if err == io.EOF {
break
}
panic(err)
}
chr := getUint16(tmp1)
if _, err := io.ReadAtLeast(r, tmp2, 1); err != nil {
panic(err)
}
b := make([]byte, int(tmp2[0]))
if _, err := io.ReadFull(r, b); err != nil {
panic(err)
}
transliterations[int(chr)] = []rune(string(b))
}
}

View File

@@ -1,71 +0,0 @@
// +build none
package main
import (
"bytes"
"compress/zlib"
"encoding/binary"
"fmt"
"go/format"
"io/ioutil"
"strconv"
"strings"
)
func main() {
data, err := ioutil.ReadFile("table.txt")
if err != nil {
panic(err)
}
var buf bytes.Buffer
for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "/*") || line == "" {
continue
}
sep := strings.IndexByte(line, ':')
if sep == -1 {
panic(line)
}
val, err := strconv.ParseInt(line[:sep], 0, 32)
if err != nil {
panic(err)
}
s, err := strconv.Unquote(line[sep+2:])
if err != nil {
panic(err)
}
if s == "" {
continue
}
if err := binary.Write(&buf, binary.LittleEndian, uint16(val)); err != nil {
panic(err)
}
if err := binary.Write(&buf, binary.LittleEndian, uint8(len(s))); err != nil {
panic(err)
}
buf.WriteString(s)
}
var cbuf bytes.Buffer
w, err := zlib.NewWriterLevel(&cbuf, zlib.BestCompression)
if err != nil {
panic(err)
}
if _, err := w.Write(buf.Bytes()); err != nil {
panic(err)
}
if err := w.Close(); err != nil {
panic(err)
}
buf.Reset()
buf.WriteString("package unidecode\n")
buf.WriteString("// AUTOGENERATED - DO NOT EDIT!\n\n")
fmt.Fprintf(&buf, "var tableData = %q;\n", cbuf.String())
dst, err := format.Source(buf.Bytes())
if err != nil {
panic(err)
}
if err := ioutil.WriteFile("table.go", dst, 0644); err != nil {
panic(err)
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +0,0 @@
// Package unidecode implements a unicode transliterator
// which replaces non-ASCII characters with their ASCII
// approximations.
package unidecode
//go:generate go run make_table.go
import (
"sync"
"unicode"
)
const pooledCapacity = 64
var (
slicePool sync.Pool
decodingOnce sync.Once
)
// Unidecode implements a unicode transliterator, which
// replaces non-ASCII characters with their ASCII
// counterparts.
// Given an unicode encoded string, returns
// another string with non-ASCII characters replaced
// with their closest ASCII counterparts.
// e.g. Unicode("áéíóú") => "aeiou"
func Unidecode(s string) string {
decodingOnce.Do(decodeTransliterations)
l := len(s)
var r []rune
if l > pooledCapacity {
r = make([]rune, 0, len(s))
} else {
if x := slicePool.Get(); x != nil {
r = x.([]rune)[:0]
} else {
r = make([]rune, 0, pooledCapacity)
}
}
for _, c := range s {
if c <= unicode.MaxASCII {
r = append(r, c)
continue
}
if c > unicode.MaxRune || c > transCount {
/* Ignore reserved chars */
continue
}
if d := transliterations[c]; d != nil {
r = append(r, d...)
}
}
res := string(r)
if l <= pooledCapacity {
slicePool.Put(r)
}
return res
}

3
vendor/github.com/segmentio/backo-go/.gitmodules generated vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "vendor/github.com/bmizerany/assert"]
path = vendor/github.com/bmizerany/assert
url = https://github.com/bmizerany/assert

21
vendor/github.com/segmentio/backo-go/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
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.

80
vendor/github.com/segmentio/backo-go/README.md generated vendored Normal file
View File

@@ -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

83
vendor/github.com/segmentio/backo-go/backo.go generated vendored Normal file
View File

@@ -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{}{}
}

3
vendor/github.com/segmentio/backo-go/go.mod generated vendored Normal file
View File

@@ -0,0 +1,3 @@
module github.com/segmentio/backo-go
go 1.13

5
vendor/github.com/xtgo/uuid/AUTHORS generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# This source file refers to The gocql Authors for copyright purposes.
Christoph Hack <christoph@tux21b.org>
Jonathan Rudenberg <jonathan@titanous.com>
Thorsten von Eicken <tve@rightscale.com>

27
vendor/github.com/xtgo/uuid/LICENSE generated vendored Normal file
View File

@@ -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.

204
vendor/github.com/xtgo/uuid/uuid.go generated vendored Normal file
View File

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

View File

@@ -21,3 +21,12 @@ _testmain.go
*.exe
*.test
*.prof
# Emacs
*~
\#*
.\#*
# Artifacts
tmp/*

View File

@@ -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

88
vendor/gopkg.in/segmentio/analytics-go.v3/History.md generated vendored Normal file
View File

@@ -0,0 +1,88 @@
v3.1.0 / 2019-09-20
===================
* add consistent panic error message
* Expose the Message interface Validate method
* return error if a custom type is enqueued
* Handle pointer types in Enqueue()
* message: update maxMessageBytes to 32KB
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

21
vendor/gopkg.in/segmentio/analytics-go.v3/License.md generated vendored Normal file
View File

@@ -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.

31
vendor/gopkg.in/segmentio/analytics-go.v3/Makefile generated vendored Normal file
View File

@@ -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' -concurrency=2 -skip='advance|alias'; fi
ci: dependencies test e2e
.PHONY: bootstrap dependencies vet test e2e ci

55
vendor/gopkg.in/segmentio/analytics-go.v3/Readme.md generated vendored Normal file
View File

@@ -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).

44
vendor/gopkg.in/segmentio/analytics-go.v3/alias.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
package analytics
import "time"
var _ Message = (*Alias)(nil)
// 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) internal() {
panic(unimplementedError)
}
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
}

431
vendor/gopkg.in/segmentio/analytics-go.v3/analytics.go generated vendored Normal file
View File

@@ -0,0 +1,431 @@
package analytics
import (
"fmt"
"io"
"io/ioutil"
"sync"
"bytes"
"encoding/json"
"net/http"
"time"
)
// Version of the client.
const Version = "3.0.0"
const unimplementedError = "not implemented"
// 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 dereferenceMessage(msg Message) Message {
switch m := msg.(type) {
case *Alias:
if m == nil {
return nil
}
return *m
case *Group:
if m == nil {
return nil
}
return *m
case *Identify:
if m == nil {
return nil
}
return *m
case *Page:
if m == nil {
return nil
}
return *m
case *Screen:
if m == nil {
return nil
}
return *m
case *Track:
if m == nil {
return nil
}
return *m
}
return msg
}
func (c *client) Enqueue(msg Message) (err error) {
msg = dereferenceMessage(msg)
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
default:
err = fmt.Errorf("messages with custom types cannot be enqueued: %T", msg)
return
}
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)
}
}
}

173
vendor/gopkg.in/segmentio/analytics-go.v3/config.go generated vendored Normal file
View File

@@ -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()
}

148
vendor/gopkg.in/segmentio/analytics-go.v3/context.go generated vendored Normal file
View File

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

60
vendor/gopkg.in/segmentio/analytics-go.v3/error.go generated vendored Normal file
View File

@@ -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")
)

53
vendor/gopkg.in/segmentio/analytics-go.v3/executor.go generated vendored Normal file
View File

@@ -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()
}

46
vendor/gopkg.in/segmentio/analytics-go.v3/group.go generated vendored Normal file
View File

@@ -0,0 +1,46 @@
package analytics
import "time"
var _ Message = (*Group)(nil)
// 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) internal() {
panic(unimplementedError)
}
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
}

37
vendor/gopkg.in/segmentio/analytics-go.v3/identify.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
package analytics
import "time"
var _ Message = (*Identify)(nil)
// 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) internal() {
panic(unimplementedError)
}
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
}

View File

@@ -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
}

87
vendor/gopkg.in/segmentio/analytics-go.v3/json.go generated vendored Normal file
View File

@@ -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
}

47
vendor/gopkg.in/segmentio/analytics-go.v3/logger.go generated vendored Normal file
View File

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

131
vendor/gopkg.in/segmentio/analytics-go.v3/message.go generated vendored Normal file
View File

@@ -0,0 +1,131 @@
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 {
// Validate 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
// internal is an unexposed interface function to ensure only types defined within this package can satisfy the Message interface. Invoking this method will panic.
internal()
}
// 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 = 32000
)

38
vendor/gopkg.in/segmentio/analytics-go.v3/page.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
package analytics
import "time"
var _ Message = (*Page)(nil)
// 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) internal() {
panic(unimplementedError)
}
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
}

117
vendor/gopkg.in/segmentio/analytics-go.v3/properties.go generated vendored Normal file
View File

@@ -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"`
}

38
vendor/gopkg.in/segmentio/analytics-go.v3/screen.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
package analytics
import "time"
var _ Message = (*Screen)(nil)
// 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) internal() {
panic(unimplementedError)
}
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
}

View File

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

View File

@@ -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
}

46
vendor/gopkg.in/segmentio/analytics-go.v3/track.go generated vendored Normal file
View File

@@ -0,0 +1,46 @@
package analytics
import "time"
var _ Message = (*Track)(nil)
// 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) internal() {
panic(unimplementedError)
}
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
}

Some files were not shown because too many files have changed in this diff Show More