mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
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:
@ -1430,6 +1430,285 @@ func TestCompleteJob(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Modules", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
templateVersionID := uuid.New()
|
||||
workspaceBuildID := uuid.New()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
job *proto.CompletedJob
|
||||
expectedResources []database.WorkspaceResource
|
||||
expectedModules []database.WorkspaceModule
|
||||
provisionerJobParams database.InsertProvisionerJobParams
|
||||
}{
|
||||
{
|
||||
name: "TemplateDryRun",
|
||||
job: &proto.CompletedJob{
|
||||
Type: &proto.CompletedJob_TemplateDryRun_{
|
||||
TemplateDryRun: &proto.CompletedJob_TemplateDryRun{
|
||||
Resources: []*sdkproto.Resource{{
|
||||
Name: "something",
|
||||
Type: "aws_instance",
|
||||
ModulePath: "module.test1",
|
||||
}, {
|
||||
Name: "something2",
|
||||
Type: "aws_instance",
|
||||
ModulePath: "",
|
||||
}},
|
||||
Modules: []*sdkproto.Module{
|
||||
{
|
||||
Key: "test1",
|
||||
Version: "1.0.0",
|
||||
Source: "github.com/example/example",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResources: []database.WorkspaceResource{{
|
||||
Name: "something",
|
||||
Type: "aws_instance",
|
||||
ModulePath: sql.NullString{
|
||||
String: "module.test1",
|
||||
Valid: true,
|
||||
},
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}, {
|
||||
Name: "something2",
|
||||
Type: "aws_instance",
|
||||
ModulePath: sql.NullString{
|
||||
String: "",
|
||||
Valid: true,
|
||||
},
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}},
|
||||
expectedModules: []database.WorkspaceModule{{
|
||||
Key: "test1",
|
||||
Version: "1.0.0",
|
||||
Source: "github.com/example/example",
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}},
|
||||
provisionerJobParams: database.InsertProvisionerJobParams{
|
||||
Type: database.ProvisionerJobTypeTemplateVersionDryRun,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TemplateImport",
|
||||
job: &proto.CompletedJob{
|
||||
Type: &proto.CompletedJob_TemplateImport_{
|
||||
TemplateImport: &proto.CompletedJob_TemplateImport{
|
||||
StartResources: []*sdkproto.Resource{{
|
||||
Name: "something",
|
||||
Type: "aws_instance",
|
||||
ModulePath: "module.test1",
|
||||
}},
|
||||
StartModules: []*sdkproto.Module{
|
||||
{
|
||||
Key: "test1",
|
||||
Version: "1.0.0",
|
||||
Source: "github.com/example/example",
|
||||
},
|
||||
},
|
||||
StopResources: []*sdkproto.Resource{{
|
||||
Name: "something2",
|
||||
Type: "aws_instance",
|
||||
ModulePath: "module.test2",
|
||||
}},
|
||||
StopModules: []*sdkproto.Module{
|
||||
{
|
||||
Key: "test2",
|
||||
Version: "2.0.0",
|
||||
Source: "github.com/example2/example",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
provisionerJobParams: database.InsertProvisionerJobParams{
|
||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
||||
Input: must(json.Marshal(provisionerdserver.TemplateVersionImportJob{
|
||||
TemplateVersionID: templateVersionID,
|
||||
})),
|
||||
},
|
||||
expectedResources: []database.WorkspaceResource{{
|
||||
Name: "something",
|
||||
Type: "aws_instance",
|
||||
ModulePath: sql.NullString{
|
||||
String: "module.test1",
|
||||
Valid: true,
|
||||
},
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}, {
|
||||
Name: "something2",
|
||||
Type: "aws_instance",
|
||||
ModulePath: sql.NullString{
|
||||
String: "module.test2",
|
||||
Valid: true,
|
||||
},
|
||||
Transition: database.WorkspaceTransitionStop,
|
||||
}},
|
||||
expectedModules: []database.WorkspaceModule{{
|
||||
Key: "test1",
|
||||
Version: "1.0.0",
|
||||
Source: "github.com/example/example",
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}, {
|
||||
Key: "test2",
|
||||
Version: "2.0.0",
|
||||
Source: "github.com/example2/example",
|
||||
Transition: database.WorkspaceTransitionStop,
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "WorkspaceBuild",
|
||||
job: &proto.CompletedJob{
|
||||
Type: &proto.CompletedJob_WorkspaceBuild_{
|
||||
WorkspaceBuild: &proto.CompletedJob_WorkspaceBuild{
|
||||
Resources: []*sdkproto.Resource{{
|
||||
Name: "something",
|
||||
Type: "aws_instance",
|
||||
ModulePath: "module.test1",
|
||||
}, {
|
||||
Name: "something2",
|
||||
Type: "aws_instance",
|
||||
ModulePath: "",
|
||||
}},
|
||||
Modules: []*sdkproto.Module{
|
||||
{
|
||||
Key: "test1",
|
||||
Version: "1.0.0",
|
||||
Source: "github.com/example/example",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResources: []database.WorkspaceResource{{
|
||||
Name: "something",
|
||||
Type: "aws_instance",
|
||||
ModulePath: sql.NullString{
|
||||
String: "module.test1",
|
||||
Valid: true,
|
||||
},
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}, {
|
||||
Name: "something2",
|
||||
Type: "aws_instance",
|
||||
ModulePath: sql.NullString{
|
||||
String: "",
|
||||
Valid: true,
|
||||
},
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}},
|
||||
expectedModules: []database.WorkspaceModule{{
|
||||
Key: "test1",
|
||||
Version: "1.0.0",
|
||||
Source: "github.com/example/example",
|
||||
Transition: database.WorkspaceTransitionStart,
|
||||
}},
|
||||
provisionerJobParams: database.InsertProvisionerJobParams{
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
|
||||
WorkspaceBuildID: workspaceBuildID,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv, db, _, pd := setup(t, false, &overrides{})
|
||||
jobParams := c.provisionerJobParams
|
||||
if jobParams.ID == uuid.Nil {
|
||||
jobParams.ID = uuid.New()
|
||||
}
|
||||
if jobParams.Provisioner == "" {
|
||||
jobParams.Provisioner = database.ProvisionerTypeEcho
|
||||
}
|
||||
if jobParams.StorageMethod == "" {
|
||||
jobParams.StorageMethod = database.ProvisionerStorageMethodFile
|
||||
}
|
||||
job, err := db.InsertProvisionerJob(ctx, jobParams)
|
||||
|
||||
tpl := dbgen.Template(t, db, database.Template{
|
||||
OrganizationID: pd.OrganizationID,
|
||||
})
|
||||
tv := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true},
|
||||
JobID: job.ID,
|
||||
})
|
||||
workspace := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
TemplateID: tpl.ID,
|
||||
})
|
||||
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
ID: workspaceBuildID,
|
||||
JobID: job.ID,
|
||||
WorkspaceID: workspace.ID,
|
||||
TemplateVersionID: tv.ID,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
_, err = db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
||||
WorkerID: uuid.NullUUID{
|
||||
UUID: pd.ID,
|
||||
Valid: true,
|
||||
},
|
||||
Types: []database.ProvisionerType{jobParams.Provisioner},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
completedJob := c.job
|
||||
completedJob.JobId = job.ID.String()
|
||||
|
||||
_, err = srv.CompleteJob(ctx, completedJob)
|
||||
require.NoError(t, err)
|
||||
|
||||
resources, err := db.GetWorkspaceResourcesByJobID(ctx, job.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resources, len(c.expectedResources))
|
||||
|
||||
for _, expectedResource := range c.expectedResources {
|
||||
for i, resource := range resources {
|
||||
if resource.Name == expectedResource.Name &&
|
||||
resource.Type == expectedResource.Type &&
|
||||
resource.ModulePath == expectedResource.ModulePath &&
|
||||
resource.Transition == expectedResource.Transition {
|
||||
resources[i] = database.WorkspaceResource{Name: "matched"}
|
||||
}
|
||||
}
|
||||
}
|
||||
// all resources should be matched
|
||||
for _, resource := range resources {
|
||||
require.Equal(t, "matched", resource.Name)
|
||||
}
|
||||
|
||||
modules, err := db.GetWorkspaceModulesByJobID(ctx, job.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, modules, len(c.expectedModules))
|
||||
|
||||
for _, expectedModule := range c.expectedModules {
|
||||
for i, module := range modules {
|
||||
if module.Key == expectedModule.Key &&
|
||||
module.Version == expectedModule.Version &&
|
||||
module.Source == expectedModule.Source &&
|
||||
module.Transition == expectedModule.Transition {
|
||||
modules[i] = database.WorkspaceModule{Key: "matched"}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, module := range modules {
|
||||
require.Equal(t, "matched", module.Key)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInsertWorkspaceResource(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user