diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index 6c2b3eb78b9ba..39f3b892c2150 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -698,6 +698,23 @@ func ConvertWorkspaceProxy(proxy database.WorkspaceProxy) WorkspaceProxy { } } +func ConvertExternalProvisioner(id uuid.UUID, tags map[string]string, provisioners []database.ProvisionerType) ExternalProvisioner { + tagsCopy := make(map[string]string, len(tags)) + for k, v := range tags { + tagsCopy[k] = v + } + strProvisioners := make([]string, 0, len(provisioners)) + for _, prov := range provisioners { + strProvisioners = append(strProvisioners, string(prov)) + } + return ExternalProvisioner{ + ID: id.String(), + Tags: tagsCopy, + 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 +722,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 +918,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,