8000 feat: Add anonymized telemetry to report product usage by kylecarbs · Pull Request #2273 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: Add anonymized telemetry to report product usage #2273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 17, 2022
Prev Previous commit
Next Next commit
Fix flake and requested changes
  • Loading branch information
kylecarbs committed Jun 13, 2022
commit 9ed5ecd88ac11fa6a28c9d401e3320eae44aee6d
10 changes: 5 additions & 5 deletions cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func server() *cobra.Command {
oauth2GithubClientSecret string
oauth2GithubAllowedOrganizations []string
oauth2GithubAllowSignups bool
telemetryEnabled bool
telemetryEnable bool
telemetryURL string
tlsCertFile string
tlsClientCAFile string
Expand Down Expand Up @@ -327,16 +327,16 @@ func server() *cobra.Command {
}
// Disable telemetry if in dev-mode. If the telemetry flag
// is manually specified, override this behavior!
if buildModeDev && !cmd.Flags().Changed("telemetry") {
telemetryEnabled = false
if buildModeDev && !cmd.Flags().Changed("telemetry-enable") {
telemetryEnable = false
}
reporter, err := telemetry.New(telemetry.Options{
DeploymentID: deploymentID,
Database: options.Database,
Logger: logger.Named("telemetry"),
URL: telemetryURL,
DevMode: dev,
Disabled: !telemetryEnabled,
Disabled: !telemetryEnable,
})
if err != nil {
return xerrors.Errorf("create telemetry reporter: %w", err)
Expand Down Expand Up @@ -580,7 +580,7 @@ func server() *cobra.Command {
"Specifies organizations the user must be a member of to authenticate with GitHub.")
cliflag.BoolVarP(root.Flags(), &oauth2GithubAllowSignups, "oauth2-github-allow-signups", "", "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS", false,
"Specifies whether new users can sign up with GitHub.")
cliflag.BoolVarP(root.Flags(), &telemetryEnabled, "telemetry", "", "CODER_TELEMETRY", true, "Specifies whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product!")
cliflag.BoolVarP(root.Flags(), &telemetryEnable, "telemetry-enable", "", "CODER_TELEMETRY_ENABLE", true, "Specifies whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product!")
cliflag.StringVarP(root.Flags(), &telemetryURL, "telemetry-url", "", "CODER_TELEMETRY_URL", "https://telemetry.coder.com", "Specifies a URL to send telemetry to.")
_ = root.Flags().MarkHidden("telemetry-url")
cliflag.BoolVarP(root.Flags(), &tlsEnable, "tls-enable", "", "CODER_TLS_ENABLE", false, "Specifies if TLS will be enabled")
Expand Down
2 changes: 1 addition & 1 deletion cli/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ func TestServer(t *testing.T) {
server := httptest.NewServer(r)
t.Cleanup(server.Close)

root, _ := clitest.New(t, "server", "--dev", "--tunnel=false", "--address", ":0", "--telemetry", "true", "--telemetry-url", server.URL)
root, _ := clitest.New(t, "server", "--dev", "--tunnel=false", "--address", ":0", "--telemetry-enable", "true", "--telemetry-url", server.URL)
var buf strings.Builder
errC := make(chan error)
root.SetOutput(&buf)
Expand Down
2 changes: 1 addition & 1 deletion coderd/database/databasefake/databasefake.go
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ func (q *fakeQuerier) GetTemplates(_ context.Context) ([]database.Template, erro
q.mutex.RLock()
defer q.mutex.RUnlock()

return q.templates, nil
return q.templates[:], nil
}

func (q *fakeQuerier) GetTemplatesByOrganization(_ context.Context, arg database.GetTemplatesByOrganizationParams) ([]database.Template, error) {
Expand Down
6 changes: 3 additions & 3 deletions coderd/database/dump.sql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion coderd/database/migrations/000023_site_config.up.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS site_config (
CREATE TABLE IF NOT EXISTS site_configs (
key varchar(256) NOT NULL UNIQUE,
value varchar(8192) NOT NULL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using jsonb as the value would make it much easier in the future to do migrations on structured data. We've needed to do this a couple times in v1.

);
4 changes: 2 additions & 2 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions coderd/database/queries/siteconfig.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-- name: InsertDeploymentID :exec
INSERT INTO site_config (key, value) VALUES ('deployment_id', $1);
INSERT INTO site_configs (key, value) VALUES ('deployment_id', $1);

-- name: GetDeploymentID :one
SELECT value FROM site_config WHERE key = 'deployment_id';
SELECT value FROM site_configs WHERE key = 'deployment_id';
21 changes: 15 additions & 6 deletions coderd/telemetry/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import (
type Options struct {
Database database.Store
Logger slog.Logger
URL *url.URL
// URL is an endpoint to direct telemetry towards!
URL *url.URL

DeploymentID string
DevMode bool
Expand Down Expand Up @@ -155,9 +156,13 @@ func (r *Reporter) runSnapshotter() {
case <-ticker.C:
}
// Skip the ticker on the first run to report instantly!
first = false
}
first = false
r.closeMutex.Lock()
if r.isClosed() {
r.closeMutex.Unlock()
return
}
r.report()
r.closeMutex.Unlock()
}
Expand Down Expand Up @@ -345,6 +350,8 @@ func (r *Reporter) createSnapshot() (*Snapshot, error) {
emailHashed := ""
atSymbol := strings.LastIndex(dbUser.Email, "@")
if atSymbol >= 0 {
// We hash the beginning of the user to allow for indexing users
// by email between deployments.
hash := sha256.Sum256([]byte(dbUser.Email[:atSymbol]))
Copy link
Contributor
@coadler coadler Jun 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hash seems easily reversible tbh. What if we just used {user_id}@domain.com?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want it to be traceable across deployments. We could use a greater hash, but I agree it's still crackable regardless.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10000

Ah yeah, I forgot about that. This seems reasonable then.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a comment here for that

emailHashed = fmt.Sprintf("%x%s", hash[:], dbUser.Email[atSymbol:])
}
Expand All @@ -366,6 +373,7 @@ func (r *Reporter) createSnapshot() (*Snapshot, error) {
snapshot.Workspaces = make([]Workspace, 0, len(workspaces))
for _, dbWorkspace := range workspaces {
snapshot.Workspaces = append(snapshot.Workspaces, Workspace{
ID: dbWorkspace.ID,
OrganizationID: dbWorkspace.OrganizationID,
OwnerID: dbWorkspace.OwnerID,
TemplateID: dbWorkspace.TemplateID,
Expand Down Expand Up @@ -475,10 +483,10 @@ type Snapshot struct {

// Deployment contains information about the host running Coder.
type Deployment struct {
ID string `json:"id" validate:"required"`
ID string `json:"id"`
Architecture string `json:"architecture"`
Containerized bool `json:"containerized"`
DevMode bool `json:"dev_mode" validate:"required"`
DevMode bool `json:"dev_mode"`
OSType string `json:"os_type"`
OSFamily string `json:"os_family"`
OSPlatform string `json:"os_platform"`
Expand All @@ -487,7 +495,7 @@ type Deployment struct {
CPUCores int `json:"cpu_cores"`
MemoryTotal uint64 `json:"memory_total"`
MachineID string `json:"machine_id"`
Version string `json:"version" validate:"required"`
Version string `json:"version"`
StartedAt time.Time `json:"started_at"`
ShutdownAt *time.Time `json:"shutdown_at"`
}
Expand Down Expand Up @@ -537,6 +545,7 @@ type WorkspaceBuild struct {
}

type Workspace struct {
ID uuid.UUID `json:"id"`
OrganizationID uuid.UUID `json:"organization_id"`
OwnerID uuid.UUID `json:"owner_id"`
TemplateID uuid.UUID `json:"template_id"`
Expand Down Expand Up @@ -578,7 +587,7 @@ type ProvisionerJob struct {
}

type ParameterSchema struct {
ID uuid.UUID `json:"parameter_schema"`
ID uuid.UUID `json:"id"`
JobID uuid.UUID `json:"job_id"`
Name string `json:"name"`
ValidationCondition string `json:"validation_condition"`
Expand Down
0