From de1a3650ce0c9b4de660433f579c3ff060805329 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 17 Oct 2023 21:26:10 +0000 Subject: [PATCH 1/2] feat: add telemetry for external provisioners --- coderd/telemetry/telemetry.go | 44 ++++++++++++++++++++----- codersdk/provisionerdaemons.go | 3 ++ enterprise/cli/provisionerdaemons.go | 3 ++ enterprise/coderd/provisionerdaemons.go | 17 +++++++++- 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 6c2b3eb78b9ba..5d2bd8f2bf74c 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/sha256" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -698,6 +699,24 @@ func ConvertWorkspaceProxy(proxy database.WorkspaceProxy) WorkspaceProxy { } } +func ConvertExternalProvisioner(id uuid.UUID, tags map[string]string, provisioners []database.ProvisionerType) ExternalProvisioner { + hashedTags := make(map[string]string, len(tags)) + for k, v := range tags { + hash := sha256.Sum256([]byte(v)) + hashedTags[k] = hex.EncodeToString(hash[:]) + } + strProvisioners := make([]string, 0, len(provisioners)) + for _, prov := range provisioners { + strProvisioners = append(strProvisioners, string(prov)) + } + return ExternalProvisioner{ + ID: id.String(), + Tags: hashedTags, + Provisioners: strProvisioners, + StartedAt: time.Now(), + } +} + // Snapshot represents a point-in-time anonymized database dump. // Data is aggregated by latest on the server-side, so partial data // can be sent without issue. @@ -705,20 +724,21 @@ type Snapshot struct { DeploymentID string `json:"deployment_id"` APIKeys []APIKey `json:"api_keys"` - ProvisionerJobs []ProvisionerJob `json:"provisioner_jobs"` + CLIInvocations []clitelemetry.Invocation `json:"cli_invocations"` + ExternalProvisioners []ExternalProvisioner `json:"external_provisioners"` Licenses []License `json:"licenses"` - Templates []Template `json:"templates"` + ProvisionerJobs []ProvisionerJob `json:"provisioner_jobs"` TemplateVersions []TemplateVersion `json:"template_versions"` + Templates []Template `json:"templates"` Users []User `json:"users"` - Workspaces []Workspace `json:"workspaces"` - WorkspaceApps []WorkspaceApp `json:"workspace_apps"` - WorkspaceAgents []WorkspaceAgent `json:"workspace_agents"` WorkspaceAgentStats []WorkspaceAgentStat `json:"workspace_agent_stats"` + WorkspaceAgents []WorkspaceAgent `json:"workspace_agents"` + WorkspaceApps []WorkspaceApp `json:"workspace_apps"` WorkspaceBuilds []WorkspaceBuild `json:"workspace_build"` - WorkspaceResources []WorkspaceResource `json:"workspace_resources"` - WorkspaceResourceMetadata []WorkspaceResourceMetadata `json:"workspace_resource_metadata"` WorkspaceProxies []WorkspaceProxy `json:"workspace_proxies"` - CLIInvocations []clitelemetry.Invocation `json:"cli_invocations"` + WorkspaceResourceMetadata []WorkspaceResourceMetadata `json:"workspace_resource_metadata"` + WorkspaceResources []WorkspaceResource `json:"workspace_resources"` + Workspaces []Workspace `json:"workspaces"` } // Deployment contains information about the host running Coder. @@ -900,6 +920,14 @@ type WorkspaceProxy struct { UpdatedAt time.Time `json:"updated_at"` } +type ExternalProvisioner struct { + ID string `json:"id"` + Tags map[string]string `json:"tags"` + Provisioners []string `json:"provisioners"` + StartedAt time.Time `json:"started_at"` + ShutdownAt *time.Time `json:"shutdown_at"` +} + type noopReporter struct{} func (*noopReporter) Report(_ *Snapshot) {} diff --git a/codersdk/provisionerdaemons.go b/codersdk/provisionerdaemons.go index ce2dd08758b8c..0b321e51662ab 100644 --- a/codersdk/provisionerdaemons.go +++ b/codersdk/provisionerdaemons.go @@ -174,6 +174,8 @@ func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after // ServeProvisionerDaemonRequest are the parameters to call ServeProvisionerDaemon with // @typescript-ignore ServeProvisionerDaemonRequest type ServeProvisionerDaemonRequest struct { + // ID is a unique ID for a provisioner daemon. + ID uuid.UUID `json:"id" format:"uuid"` // Organization is the organization for the URL. At present provisioner daemons ARE NOT scoped to organizations // and so the organization ID is optional. Organization uuid.UUID `json:"organization" format:"uuid"` @@ -194,6 +196,7 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione return nil, xerrors.Errorf("parse url: %w", err) } query := serverURL.Query() + query.Add("id", req.ID.String()) for _, provisioner := range req.Provisioners { query.Add("provisioner", string(provisioner)) } diff --git a/enterprise/cli/provisionerdaemons.go b/enterprise/cli/provisionerdaemons.go index 837cb2e671766..623368487ff68 100644 --- a/enterprise/cli/provisionerdaemons.go +++ b/enterprise/cli/provisionerdaemons.go @@ -9,6 +9,7 @@ import ( "os/signal" "time" + "github.com/google/uuid" "golang.org/x/xerrors" "cdr.dev/slog" @@ -127,8 +128,10 @@ func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd { connector := provisionerd.LocalProvisioners{ string(database.ProvisionerTypeTerraform): proto.NewDRPCProvisionerClient(terraformClient), } + id := uuid.New() srv := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) { return client.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{ + ID: id, Provisioners: []codersdk.ProvisionerType{ codersdk.ProvisionerTypeTerraform, }, diff --git a/enterprise/coderd/provisionerdaemons.go b/enterprise/coderd/provisionerdaemons.go index 70f59f40308f0..e10489f45a6c7 100644 --- a/enterprise/coderd/provisionerdaemons.go +++ b/enterprise/coderd/provisionerdaemons.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "strings" + "time" "github.com/google/uuid" "github.com/hashicorp/yamux" @@ -27,6 +28,8 @@ import ( "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/provisionerdserver" "github.com/coder/coder/v2/coderd/rbac" + "github.com/coder/coder/v2/coderd/telemetry" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisionerd/proto" ) @@ -155,6 +158,11 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) return } + id, _ := uuid.Parse(r.URL.Query().Get("id")) + if id == uuid.Nil { + id = uuid.New() + } + provisionersMap := map[codersdk.ProvisionerType]struct{}{} for _, provisioner := range r.URL.Query()["provisioner"] { switch provisioner { @@ -210,6 +218,13 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) api.AGPL.WebsocketWaitMutex.Unlock() defer api.AGPL.WebsocketWaitGroup.Done() + tep := telemetry.ConvertExternalProvisioner(id, tags, provisioners) + api.Telemetry.Report(&telemetry.Snapshot{ExternalProvisioners: []telemetry.ExternalProvisioner{tep}}) + defer func() { + tep.ShutdownAt = ptr.Ref(time.Now()) + api.Telemetry.Report(&telemetry.Snapshot{ExternalProvisioners: []telemetry.ExternalProvisioner{tep}}) + }() + conn, err := websocket.Accept(rw, r, &websocket.AcceptOptions{ // Need to disable compression to avoid a data-race. CompressionMode: websocket.CompressionDisabled, @@ -245,7 +260,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) srv, err := provisionerdserver.NewServer( api.ctx, api.AccessURL, - uuid.New(), + id, logger, provisioners, tags, From 1095dafc10c8475fc79dff0edeab4974a01d6e06 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Wed, 18 Oct 2023 19:07:03 +0000 Subject: [PATCH 2/2] don't hash tag values --- coderd/telemetry/telemetry.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 5d2bd8f2bf74c..39f3b892c2150 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "crypto/sha256" - "encoding/hex" "encoding/json" "errors" "fmt" @@ -700,10 +699,9 @@ func ConvertWorkspaceProxy(proxy database.WorkspaceProxy) WorkspaceProxy { } func ConvertExternalProvisioner(id uuid.UUID, tags map[string]string, provisioners []database.ProvisionerType) ExternalProvisioner { - hashedTags := make(map[string]string, len(tags)) + tagsCopy := make(map[string]string, len(tags)) for k, v := range tags { - hash := sha256.Sum256([]byte(v)) - hashedTags[k] = hex.EncodeToString(hash[:]) + tagsCopy[k] = v } strProvisioners := make([]string, 0, len(provisioners)) for _, prov := range provisioners { @@ -711,7 +709,7 @@ func ConvertExternalProvisioner(id uuid.UUID, tags map[string]string, provisione } return ExternalProvisioner{ ID: id.String(), - Tags: hashedTags, + Tags: tagsCopy, Provisioners: strProvisioners, StartedAt: time.Now(), }