diff --git a/Makefile b/Makefile index d5846d55..5090adc7 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ # This makefile is meant for humans VERSION := $(shell git describe --tags --always --dirty="-dev") -LDFLAGS := -ldflags='-X "main.Version=$(VERSION)"' +ANALYTICS_WRITE_KEY ?= +LDFLAGS := -ldflags='-X "main.Version=$(VERSION)" -X "main.AnalyticsWriteKey=$(ANALYTICS_WRITE_KEY)"' test: GO111MODULE=on go test -v ./... diff --git a/README.md b/README.md index f9819e9b..cd7c9222 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,10 @@ To configure chamber to use the S3 backend, set `CHAMBER_SECRET_BACKEND` to `S3` This feature is experimental, and not currently meant for production work. +## Analytics + +`chamber` includes some usage analytics code which Segment uses internally for tracking usage of internal tools. This analytics code is turned off by default, and can only be enabled via a linker flag at build time, which we do not set for public github releases. + ## Releasing To cut a new release, just push a tag named `v` where `` is a diff --git a/cmd/delete.go b/cmd/delete.go index d690f3c2..c5c67f4e 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/segmentio/chamber/store" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) // deleteCmd represents the delete command @@ -31,6 +32,18 @@ func delete(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to validate key") } + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Enqueue(analytics.Track{ + UserId: username, + Event: "Ran Command", + Properties: analytics.NewProperties(). + Set("command", "delete"). + Set("chamber-version", chamberVersion). + Set("service", service). + Set("key", key). + Set("backend", backend), + }) + } secretStore, err := getSecretStore() if err != nil { return errors.Wrap(err, "Failed to get secret store") diff --git a/cmd/exec.go b/cmd/exec.go index b2bef4ea..cf66f26a 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) // execCmd represents the exec command @@ -37,6 +38,18 @@ func execRun(cmd *cobra.Command, args []string) error { dashIx := cmd.ArgsLenAtDash() services, command, commandArgs := args[:dashIx], args[dashIx], args[dashIx+1:] + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Enqueue(analytics.Track{ + UserId: username, + Event: "Ran Command", + Properties: analytics.NewProperties(). + Set("command", "exec"). + Set("chamber-version", chamberVersion). + Set("services", services). + Set("backend", backend), + }) + } + env := environ(os.Environ()) secretStore, err := getSecretStore() if err != nil { diff --git a/cmd/export.go b/cmd/export.go index abd3d04a..48ad37e9 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -13,6 +13,7 @@ import ( "github.com/magiconair/properties" "github.com/pkg/errors" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) // exportCmd represents the export command @@ -37,6 +38,18 @@ func init() { func runExport(cmd *cobra.Command, args []string) error { var err error + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Enqueue(analytics.Track{ + UserId: username, + Event: "Ran Command", + Properties: analytics.NewProperties(). + Set("command", "export"). + Set("chamber-version", chamberVersion). + Set("services", args). + Set("backend", backend), + }) + } + secretStore, err := getSecretStore() if err != nil { return err diff --git a/cmd/history.go b/cmd/history.go index 4f592847..635cdcf9 100644 --- a/cmd/history.go +++ b/cmd/history.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/segmentio/chamber/store" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) // historyCmd represents the history command @@ -34,6 +35,19 @@ func history(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to validate key") } + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Enqueue(analytics.Track{ + UserId: username, + Event: "Ran Command", + Properties: analytics.NewProperties(). + Set("command", "history"). + Set("chamber-version", chamberVersion). + Set("service", service). + Set("key", key). + Set("backend", backend), + }) + } + secretStore, err := getSecretStore() if err != nil { return errors.Wrap(err, "Failed to get secret store") diff --git a/cmd/import.go b/cmd/import.go index bcb1fac6..aa2317c9 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/segmentio/chamber/store" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) var ( @@ -51,6 +52,18 @@ func importRun(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to decode input as json") } + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Enqueue(analytics.Track{ + UserId: username, + Event: "Ran Command", + Properties: analytics.NewProperties(). + Set("command", "import"). + Set("chamber-version", chamberVersion). + Set("service", service). + Set("backend", backend), + }) + } + secretStore, err := getSecretStore() if err != nil { return errors.Wrap(err, "Failed to get secret store") diff --git a/cmd/list.go b/cmd/list.go index ff88ff91..84cbd635 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) // listCmd represents the list command @@ -33,11 +34,22 @@ func list(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to validate service") } + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Enqueue(analytics.Track{ + UserId: username, + Event: "Ran Command", + Properties: analytics.NewProperties(). + Set("command", "list"). + Set("chamber-version", chamberVersion). + Set("service", service). + Set("backend", backend), + }) + } + secretStore, err := getSecretStore() if err != nil { return errors.Wrap(err, "Failed to get secret store") } - secrets, err := secretStore.List(service, withValues) if err != nil { return errors.Wrap(err, "Failed to list store contents") diff --git a/cmd/read.go b/cmd/read.go index f9bef3e6..fffd1a04 100644 --- a/cmd/read.go +++ b/cmd/read.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/segmentio/chamber/store" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) var ( @@ -41,6 +42,19 @@ func read(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to validate key") } + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Enqueue(analytics.Track{ + UserId: username, + Event: "Ran Command", + Properties: analytics.NewProperties(). + Set("command", "read"). + Set("chamber-version", chamberVersion). + Set("service", service). + Set("key", key). + Set("backend", backend), + }) + } + secretStore, err := getSecretStore() if err != nil { return errors.Wrap(err, "Failed to get secret store") diff --git a/cmd/root.go b/cmd/root.go index 814caefd..09201d2a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,7 @@ import ( "github.com/segmentio/chamber/store" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) // Regex's used to validate service and key names @@ -19,6 +20,12 @@ var ( verbose bool numRetries int chamberVersion string + backend string + + analyticsEnabled bool + analyticsWriteKey string + analyticsClient analytics.Client + username string ) const ( @@ -40,9 +47,11 @@ var Backends = []string{SSMBackend, S3Backend} // RootCmd represents the base command when called without any subcommands var RootCmd = &cobra.Command{ - Use: "chamber", - Short: "CLI for storing secrets", - SilenceUsage: true, + Use: "chamber", + Short: "CLI for storing secrets", + SilenceUsage: true, + PersistentPreRun: prerun, + PersistentPostRun: postrun, } func init() { @@ -52,8 +61,12 @@ func init() { // Execute adds all child commands to the root command sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute(vers string) { +func Execute(vers string, writeKey string) { chamberVersion = vers + + analyticsWriteKey = writeKey + analyticsEnabled = analyticsWriteKey != "" + if cmd, err := RootCmd.ExecuteC(); err != nil { if strings.Contains(err.Error(), "arg(s)") || strings.Contains(err.Error(), "usage") { cmd.Usage() @@ -99,3 +112,27 @@ func getSecretStore() (store.Store, error) { } return s, err } + +func prerun(cmd *cobra.Command, args []string) { + backend = strings.ToUpper(os.Getenv(BackendEnvVar)) + + if analyticsEnabled { + // set up analytics client + analyticsClient, _ = analytics.NewWithConfig(analyticsWriteKey, analytics.Config{ + BatchSize: 1, + }) + + username = os.Getenv("USER") + analyticsClient.Enqueue(analytics.Identify{ + UserId: username, + Traits: analytics.NewTraits(). + Set("chamber-version", chamberVersion), + }) + } +} + +func postrun(cmd *cobra.Command, args []string) { + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Close() + } +} diff --git a/cmd/version.go b/cmd/version.go index 997863a3..51d047e1 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -5,6 +5,7 @@ import ( "os" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) // versionCmd represents the version command @@ -20,5 +21,15 @@ func init() { func versionRun(cmd *cobra.Command, args []string) error { fmt.Fprintf(os.Stdout, "chamber %s\n", chamberVersion) + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Enqueue(analytics.Track{ + UserId: username, + Event: "Ran Command", + Properties: analytics.NewProperties(). + Set("command", "version"). + Set("chamber-version", chamberVersion). + Set("backend", backend), + }) + } return nil } diff --git a/cmd/write.go b/cmd/write.go index 44c52cf6..482e2c8b 100644 --- a/cmd/write.go +++ b/cmd/write.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/segmentio/chamber/store" "github.com/spf13/cobra" + analytics "gopkg.in/segmentio/analytics-go.v3" ) var ( @@ -39,6 +40,19 @@ func write(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to validate key") } + if analyticsEnabled && analyticsClient != nil { + analyticsClient.Enqueue(analytics.Track{ + UserId: username, + Event: "Ran Command", + Properties: analytics.NewProperties(). + Set("command", "write"). + Set("chamber-version", chamberVersion). + Set("service", service). + Set("backend", backend). + Set("key", key), + }) + } + value := args[2] if value == "-" { // Read value from standard input diff --git a/go.mod b/go.mod index 706d1cf1..e51c6ece 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,10 @@ require ( github.com/magiconair/properties v1.8.0 github.com/pkg/errors v0.8.0 github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.2 // indirect github.com/stretchr/testify v1.2.2 + github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect + gopkg.in/segmentio/analytics-go.v3 v3.0.0 ) diff --git a/go.sum b/go.sum index deb54aab..5921ee36 100644 --- a/go.sum +++ b/go.sum @@ -12,9 +12,15 @@ github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c h1:rsRTAcCR5CeNLkvgBVSjQoDGRRt6kggsE6XYBqCv2KQ= +github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +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= +gopkg.in/segmentio/analytics-go.v3 v3.0.0 h1:CyPSnB9Y3MhF/lL71ARYukL7Si55KADW9fGJFDEPG0E= +gopkg.in/segmentio/analytics-go.v3 v3.0.0/go.mod h1:4QqqlTlSSpVlWA9/9nDcPw+FkM2yv1NQoYjUbL9/JAw= diff --git a/main.go b/main.go index f0d62f9c..197b00a0 100644 --- a/main.go +++ b/main.go @@ -4,9 +4,10 @@ import "github.com/segmentio/chamber/cmd" var ( // This is updated by linker flags during build - Version = "dev" + Version = "dev" + AnalyticsWriteKey = "" ) func main() { - cmd.Execute(Version) + cmd.Execute(Version, AnalyticsWriteKey) } diff --git a/vendor/vendor.json b/vendor/vendor.json index 4e431320..ac22a461 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -270,6 +270,12 @@ "revision": "9f9027faeb0dad515336ed2f28317f9f8f527ab4", "revisionTime": "2016-01-29T19:31:06Z" }, + { + "checksumSHA1": "jK8cjKr2eA3JiQP+T4HLjQRV5ak=", + "path": "github.com/segmentio/backo-go", + "revision": "204274ad699c0983a70203a566887f17a717fef4", + "revisionTime": "2016-04-24T05:23:52Z" + }, { "checksumSHA1": "aG5wPXVGAEu90TjPFNZFRtox2Zo=", "path": "github.com/spf13/cobra", @@ -287,6 +293,18 @@ "path": "github.com/stretchr/testify/assert", "revision": "9f9027faeb0dad515336ed2f28317f9f8f527ab4", "revisionTime": "2016-01-29T19:31:06Z" + }, + { + "checksumSHA1": "LAL/r7KfCdqp4M6MM13+7NWsUNc=", + "path": "github.com/xtgo/uuid", + "revision": "a0b114877d4caeffbd7f87e3757c17fce570fea7", + "revisionTime": "2014-08-04T02:12:11Z" + }, + { + "checksumSHA1": "iHUiOk4poaZqsPTDedI9I9epcVU=", + "path": "gopkg.in/segmentio/analytics-go.v3", + "revision": "1178b964a36694a8f9c161b19e6fe28cb37e8482", + "revisionTime": "2017-12-07T18:07:11Z" } ], "rootPath": "github.com/segmentio/chamber"