Skip to content

Commit 9ab248d

Browse files
committed
feat: add telemetry for external provisioners
1 parent 09d995c commit 9ab248d

File tree

4 files changed

+58
-9
lines changed

4 files changed

+58
-9
lines changed

coderd/telemetry/telemetry.go

+36-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"crypto/sha256"
7+
"encoding/hex"
78
"encoding/json"
89
"errors"
910
"fmt"
@@ -698,27 +699,46 @@ func ConvertWorkspaceProxy(proxy database.WorkspaceProxy) WorkspaceProxy {
698699
}
699700
}
700701

702+
func ConvertExternalProvisioner(id uuid.UUID, tags map[string]string, provisioners []database.ProvisionerType) ExternalProvisioner {
703+
hashedTags := make(map[string]string, len(tags))
704+
for k, v := range tags {
705+
hash := sha256.Sum256([]byte(v))
706+
hashedTags[k] = hex.EncodeToString(hash[:])
707+
}
708+
strProvisioners := make([]string, 0, len(provisioners))
709+
for _, prov := range provisioners {
710+
strProvisioners = append(strProvisioners, string(prov))
711+
}
712+
return ExternalProvisioner{
713+
ID: id.String(),
714+
Tags: hashedTags,
715+
Provisioners: strProvisioners,
716+
StartedAt: time.Now(),
717+
}
718+
}
719+
701720
// Snapshot represents a point-in-time anonymized database dump.
702721
// Data is aggregated by latest on the server-side, so partial data
703722
// can be sent without issue.
704723
type Snapshot struct {
705724
DeploymentID string `json:"deployment_id"`
706725

707726
APIKeys []APIKey `json:"api_keys"`
708-
ProvisionerJobs []ProvisionerJob `json:"provisioner_jobs"`
727+
CLIInvocations []clitelemetry.Invocation `json:"cli_invocations"`
728+
ExternalProvisioners []ExternalProvisioner `json:"external_provisioners"`
709729
Licenses []License `json:"licenses"`
710-
Templates []Template `json:"templates"`
730+
ProvisionerJobs []ProvisionerJob `json:"provisioner_jobs"`
711731
TemplateVersions []TemplateVersion `json:"template_versions"`
732+
Templates []Template `json:"templates"`
712733
Users []User `json:"users"`
713-
Workspaces []Workspace `json:"workspaces"`
714-
WorkspaceApps []WorkspaceApp `json:"workspace_apps"`
715-
WorkspaceAgents []WorkspaceAgent `json:"workspace_agents"`
716734
WorkspaceAgentStats []WorkspaceAgentStat `json:"workspace_agent_stats"`
735+
WorkspaceAgents []WorkspaceAgent `json:"workspace_agents"`
736+
WorkspaceApps []WorkspaceApp `json:"workspace_apps"`
717737
WorkspaceBuilds []WorkspaceBuild `json:"workspace_build"`
718-
WorkspaceResources []WorkspaceResource `json:"workspace_resources"`
719-
WorkspaceResourceMetadata []WorkspaceResourceMetadata `json:"workspace_resource_metadata"`
720738
WorkspaceProxies []WorkspaceProxy `json:"workspace_proxies"`
721-
CLIInvocations []clitelemetry.Invocation `json:"cli_invocations"`
739+
WorkspaceResourceMetadata []WorkspaceResourceMetadata `json:"workspace_resource_metadata"`
740+
WorkspaceResources []WorkspaceResource `json:"workspace_resources"`
741+
Workspaces []Workspace `json:"workspaces"`
722742
}
723743

724744
// Deployment contains information about the host running Coder.
@@ -900,6 +920,14 @@ type WorkspaceProxy struct {
900920
UpdatedAt time.Time `json:"updated_at"`
901921
}
902922

923+
type ExternalProvisioner struct {
924+
ID string `json:"id"`
925+
Tags map[string]string `json:"tags"`
926+
Provisioners []string `json:"provisioners"`
927+
StartedAt time.Time `json:"started_at"`
928+
ShutdownAt *time.Time `json:"shutdown_at"`
929+
}
930+
903931
type noopReporter struct{}
904932

905933
func (*noopReporter) Report(_ *Snapshot) {}

codersdk/provisionerdaemons.go

+3
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after
174174
// ServeProvisionerDaemonRequest are the parameters to call ServeProvisionerDaemon with
175175
// @typescript-ignore ServeProvisionerDaemonRequest
176176
type ServeProvisionerDaemonRequest struct {
177+
// ID is a unique ID for a provisioner daemon.
178+
ID uuid.UUID `json:"id" format:"uuid"`
177179
// Organization is the organization for the URL. At present provisioner daemons ARE NOT scoped to organizations
178180
// and so the organization ID is optional.
179181
Organization uuid.UUID `json:"organization" format:"uuid"`
@@ -194,6 +196,7 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione
194196
return nil, xerrors.Errorf("parse url: %w", err)
195197
}
196198
query := serverURL.Query()
199+
query.Add("id", req.ID.String())
197200
for _, provisioner := range req.Provisioners {
198201
query.Add("provisioner", string(provisioner))
199202
}

enterprise/cli/provisionerdaemons.go

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os/signal"
1010
"time"
1111

12+
"github.com/google/uuid"
1213
"golang.org/x/xerrors"
1314

1415
"cdr.dev/slog"
@@ -127,8 +128,10 @@ func (r *RootCmd) provisionerDaemonStart() *clibase.Cmd {
127128
connector := provisionerd.LocalProvisioners{
128129
string(database.ProvisionerTypeTerraform): proto.NewDRPCProvisionerClient(terraformClient),
129130
}
131+
id := uuid.New()
130132
srv := provisionerd.New(func(ctx context.Context) (provisionerdproto.DRPCProvisionerDaemonClient, error) {
131133
return client.ServeProvisionerDaemon(ctx, codersdk.ServeProvisionerDaemonRequest{
134+
ID: id,
132135
Provisioners: []codersdk.ProvisionerType{
133136
codersdk.ProvisionerTypeTerraform,
134137
},

enterprise/coderd/provisionerdaemons.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net"
1111
"net/http"
1212
"strings"
13+
"time"
1314

1415
"github.com/google/uuid"
1516
"github.com/hashicorp/yamux"
@@ -27,6 +28,8 @@ import (
2728
"github.com/coder/coder/v2/coderd/httpmw"
2829
"github.com/coder/coder/v2/coderd/provisionerdserver"
2930
"github.com/coder/coder/v2/coderd/rbac"
31+
"github.com/coder/coder/v2/coderd/telemetry"
32+
"github.com/coder/coder/v2/coderd/util/ptr"
3033
"github.com/coder/coder/v2/codersdk"
3134
"github.com/coder/coder/v2/provisionerd/proto"
3235
)
@@ -155,6 +158,11 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
155158
return
156159
}
157160

161+
id, _ := uuid.Parse(r.URL.Query().Get("id"))
162+
if id == uuid.Nil {
163+
id = uuid.New()
164+
}
165+
158166
provisionersMap := map[codersdk.ProvisionerType]struct{}{}
159167
for _, provisioner := range r.URL.Query()["provisioner"] {
160168
switch provisioner {
@@ -210,6 +218,13 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
210218
api.AGPL.WebsocketWaitMutex.Unlock()
211219
defer api.AGPL.WebsocketWaitGroup.Done()
212220

221+
tep := telemetry.ConvertExternalProvisioner(id, tags, provisioners)
222+
api.Telemetry.Report(&telemetry.Snapshot{ExternalProvisioners: []telemetry.ExternalProvisioner{tep}})
223+
defer func() {
224+
tep.ShutdownAt = ptr.Ref(time.Now())
225+
api.Telemetry.Report(&telemetry.Snapshot{ExternalProvisioners: []telemetry.ExternalProvisioner{tep}})
226+
}()
227+
213228
conn, err := websocket.Accept(rw, r, &websocket.AcceptOptions{
214229
// Need to disable compression to avoid a data-race.
215230
CompressionMode: websocket.CompressionDisabled,
@@ -245,7 +260,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
245260
srv, err := provisionerdserver.NewServer(
246261
api.ctx,
247262
api.AccessURL,
248-
uuid.New(),
263+
id,
249264
logger,
250265
provisioners,
251266
tags,

0 commit comments

Comments
 (0)