Skip to content

Commit a672ae8

Browse files
authored
feat: Extract instance type when provisioning VMs (coder#4839)
This should help us identify what instances our users consume.
1 parent 26a920a commit a672ae8

12 files changed

+260
-152
lines changed

coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE workspace_resources
2+
DROP COLUMN instance_type;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE workspace_resources
2+
ADD COLUMN instance_type varchar(256);

coderd/database/models.go

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

Lines changed: 21 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/workspaceresources.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ SELECT * FROM workspace_resources WHERE created_at > $1;
2727

2828
-- name: InsertWorkspaceResource :one
2929
INSERT INTO
30-
workspace_resources (id, created_at, job_id, transition, type, name, hide, icon)
30+
workspace_resources (id, created_at, job_id, transition, type, name, hide, icon, instance_type)
3131
VALUES
32-
($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;
32+
($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;
3333

3434
-- name: GetWorkspaceResourceMetadataByResourceID :many
3535
SELECT

coderd/provisionerdaemons.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,10 @@ func insertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
750750
Name: protoResource.Name,
751751
Hide: protoResource.Hide,
752752
Icon: protoResource.Icon,
753+
InstanceType: sql.NullString{
754+
String: protoResource.InstanceType,
755+
Valid: protoResource.InstanceType != "",
756+
},
753757
})
754758
if err != nil {
755759
return xerrors.Errorf("insert provisioner job resource %q: %w", protoResource.Name, err)

coderd/telemetry/telemetry.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -539,10 +539,11 @@ func ConvertWorkspaceApp(app database.WorkspaceApp) WorkspaceApp {
539539
// ConvertWorkspaceResource anonymizes a workspace resource.
540540
func ConvertWorkspaceResource(resource database.WorkspaceResource) WorkspaceResource {
541541
return WorkspaceResource{
542-
ID: resource.ID,
543-
JobID: resource.JobID,
544-
Transition: resource.Transition,
545-
Type: resource.Type,
542+
ID: resource.ID,
543+
JobID: resource.JobID,
544+
Transition: resource.Transition,
545+
Type: resource.Type,
546+
InstanceType: resource.InstanceType.String,
546547
}
547548
}
548549

@@ -667,10 +668,11 @@ type User struct {
667668
}
668669

669670
type WorkspaceResource struct {
670-
ID uuid.UUID `json:"id"`
671-
JobID uuid.UUID `json:"job_id"`
672-
Transition database.WorkspaceTransition `json:"transition"`
673-
Type string `json:"type"`
671+
ID uuid.UUID `json:"id"`
672+
JobID uuid.UUID `json:"job_id"`
673+
Transition database.WorkspaceTransition `json:"transition"`
674+
Type string `json:"type"`
675+
InstanceType string `json:"instance_type"`
674676
}
675677

676678
type WorkspaceResourceMetadata struct {

provisioner/terraform/resources.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -382,12 +382,13 @@ func ConvertResources(module *tfjson.StateModule, rawGraph string) ([]*proto.Res
382382
}
383383

384384
resources = append(resources, &proto.Resource{
385-
Name: resource.Name,
386-
Type: resource.Type,
387-
Agents: agents,
388-
Hide: resourceHidden[label],
389-
Icon: resourceIcon[label],
390-
Metadata: resourceMetadata[label],
385+
Name: resource.Name,
386+
Type: resource.Type,
387+
Agents: agents,
388+
Hide: resourceHidden[label],
389+
Icon: resourceIcon[label],
390+
Metadata: resourceMetadata[label],
391+
InstanceType: applyInstanceType(resource),
391392
})
392393
}
393394

@@ -405,6 +406,31 @@ type graphResource struct {
405406
Depth uint
406407
}
407408

409+
// applyInstanceType sets the instance type on an agent if it matches
410+
// one of the special resource types that we track.
411+
func applyInstanceType(resource *tfjson.StateResource) string {
412+
key, isValid := map[string]string{
413+
"google_compute_instance": "machine_type",
414+
"aws_instance": "instance_type",
415+
"aws_spot_instance_request": "instance_type",
416+
"azurerm_linux_virtual_machine": "size",
417+
"azurerm_windows_virtual_machine": "size",
418+
}[resource.Type]
419+
if !isValid {
420+
return ""
421+
}
422+
423+
instanceTypeRaw, isValid := resource.AttributeValues[key]
424+
if !isValid {
425+
return ""
426+
}
427+
instanceType, isValid := instanceTypeRaw.(string)
428+
if !isValid {
429+
return ""
430+
}
431+
return instanceType
432+
}
433+
408434
// applyAutomaticInstanceID checks if the resource is one of a set of *magical* IDs
409435
// that automatically index their identifier for automatic authentication.
410436
func applyAutomaticInstanceID(resource *tfjson.StateResource, agents []*proto.Agent) {

provisioner/terraform/resources_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,58 @@ func TestAppSlugValidation(t *testing.T) {
287287
require.ErrorContains(t, err, "duplicate app slug")
288288
}
289289

290+
func TestInstanceTypeAssociation(t *testing.T) {
291+
t.Parallel()
292+
type tc struct {
293+
ResourceType string
294+
InstanceTypeKey string
295+
}
296+
for _, tc := range []tc{{
297+
ResourceType: "google_compute_instance",
298+
InstanceTypeKey: "machine_type",
299+
}, {
300+
ResourceType: "aws_instance",
301+
InstanceTypeKey: "instance_type",
302+
}, {
303+
ResourceType: "aws_spot_instance_request",
304+
InstanceTypeKey: "instance_type",
305+
}, {
306+
ResourceType: "azurerm_linux_virtual_machine",
307+
InstanceTypeKey: "size",
308+
}, {
309+
ResourceType: "azurerm_windows_virtual_machine",
310+
InstanceTypeKey: "size",
311+
}} {
312+
tc := tc
313+
t.Run(tc.ResourceType, func(t *testing.T) {
314+
t.Parallel()
315+
instanceType, err := cryptorand.String(12)
316+
require.NoError(t, err)
317+
resources, err := terraform.ConvertResources(&tfjson.StateModule{
318+
Resources: []*tfjson.StateResource{{
319+
Address: tc.ResourceType + ".dev",
320+
Type: tc.ResourceType,
321+
Name: "dev",
322+
Mode: tfjson.ManagedResourceMode,
323+
AttributeValues: map[string]interface{}{
324+
tc.InstanceTypeKey: instanceType,
325+
},
326+
}},
327+
// This is manually created to join the edges.
328+
}, `digraph {
329+
compound = "true"
330+
newrank = "true"
331+
subgraph "root" {
332+
"[root] `+tc.ResourceType+`.dev" [label = "`+tc.ResourceType+`.dev", shape = "box"]
333+
}
334+
}`)
335+
require.NoError(t, err)
336+
require.Len(t, resources, 1)
337+
require.Equal(t, resources[0].GetInstanceType(), instanceType)
338+
})
339+
}
340+
}
341+
290342
func TestInstanceIDAssociation(t *testing.T) {
291343
t.Parallel()
292344
type tc struct {

0 commit comments

Comments
 (0)