chore: track terraform modules in telemetry (#15450)

Addresses https://github.com/coder/nexus/issues/35.

This PR:

- Adds a `workspace_modules` table to track modules used by the
Terraform provisioner in provisioner jobs.
- Adds a `module_path` column to the `workspace_resources` table,
allowing to identify which module a resource originates from.
- Starts pushing this new information into telemetry.

For the person reviewing this PR, do not fret about the 1,500 new lines
- ~1,000 of them are auto-generated.
This commit is contained in:
Hugo Dutka
2024-11-16 21:56:19 +01:00
committed by GitHub
parent 968c52bc36
commit aa0dc2daa1
35 changed files with 1633 additions and 412 deletions

View File

@ -456,6 +456,17 @@ func (r *remoteReporter) createSnapshot() (*Snapshot, error) {
}
return nil
})
eg.Go(func() error {
workspaceModules, err := r.options.Database.GetWorkspaceModulesCreatedAfter(ctx, createdAfter)
if err != nil {
return xerrors.Errorf("get workspace modules: %w", err)
}
snapshot.WorkspaceModules = make([]WorkspaceModule, 0, len(workspaceModules))
for _, module := range workspaceModules {
snapshot.WorkspaceModules = append(snapshot.WorkspaceModules, ConvertWorkspaceModule(module))
}
return nil
})
eg.Go(func() error {
licenses, err := r.options.Database.GetUnexpiredLicenses(ctx)
if err != nil {
@ -642,7 +653,7 @@ func ConvertWorkspaceApp(app database.WorkspaceApp) WorkspaceApp {
// ConvertWorkspaceResource anonymizes a workspace resource.
func ConvertWorkspaceResource(resource database.WorkspaceResource) WorkspaceResource {
return WorkspaceResource{
r := WorkspaceResource{
ID: resource.ID,
JobID: resource.JobID,
CreatedAt: resource.CreatedAt,
@ -650,6 +661,10 @@ func ConvertWorkspaceResource(resource database.WorkspaceResource) WorkspaceReso
Type: resource.Type,
InstanceType: resource.InstanceType.String,
}
if resource.ModulePath.Valid {
r.ModulePath = &resource.ModulePath.String
}
return r
}
// ConvertWorkspaceResourceMetadata anonymizes workspace metadata.
@ -661,6 +676,29 @@ func ConvertWorkspaceResourceMetadata(metadata database.WorkspaceResourceMetadat
}
}
func shouldSendRawModuleSource(source string) bool {
return strings.Contains(source, "registry.coder.com")
}
func ConvertWorkspaceModule(module database.WorkspaceModule) WorkspaceModule {
source := module.Source
version := module.Version
if !shouldSendRawModuleSource(source) {
source = fmt.Sprintf("%x", sha256.Sum256([]byte(source)))
version = fmt.Sprintf("%x", sha256.Sum256([]byte(version)))
}
return WorkspaceModule{
ID: module.ID,
JobID: module.JobID,
Transition: module.Transition,
Source: source,
Version: version,
Key: module.Key,
CreatedAt: module.CreatedAt,
}
}
// ConvertUser anonymizes a user.
func ConvertUser(dbUser database.User) User {
emailHashed := ""
@ -810,6 +848,7 @@ type Snapshot struct {
WorkspaceProxies []WorkspaceProxy `json:"workspace_proxies"`
WorkspaceResourceMetadata []WorkspaceResourceMetadata `json:"workspace_resource_metadata"`
WorkspaceResources []WorkspaceResource `json:"workspace_resources"`
WorkspaceModules []WorkspaceModule `json:"workspace_modules"`
Workspaces []Workspace `json:"workspaces"`
NetworkEvents []NetworkEvent `json:"network_events"`
}
@ -878,6 +917,11 @@ type WorkspaceResource struct {
Transition database.WorkspaceTransition `json:"transition"`
Type string `json:"type"`
InstanceType string `json:"instance_type"`
// ModulePath is nullable because it was added a long time after the
// original workspace resource telemetry was added. All new resources
// will have a module path, but deployments with older resources still
// in the database will not.
ModulePath *string `json:"module_path"`
}
type WorkspaceResourceMetadata struct {
@ -886,6 +930,16 @@ type WorkspaceResourceMetadata struct {
Sensitive bool `json:"sensitive"`
}
type WorkspaceModule struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
JobID uuid.UUID `json:"job_id"`
Transition database.WorkspaceTransition `json:"transition"`
Key string `json:"key"`
Version string `json:"version"`
Source string `json:"source"`
}
type WorkspaceAgent struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`