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
+21
View File
@@ -2666,6 +2666,20 @@ func (q *querier) GetWorkspaceByWorkspaceAppID(ctx context.Context, workspaceApp
return fetch(q.log, q.auth, q.db.GetWorkspaceByWorkspaceAppID)(ctx, workspaceAppID)
}
func (q *querier) GetWorkspaceModulesByJobID(ctx context.Context, jobID uuid.UUID) ([]database.WorkspaceModule, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
return nil, err
}
return q.db.GetWorkspaceModulesByJobID(ctx, jobID)
}
func (q *querier) GetWorkspaceModulesCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceModule, error) {
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
return nil, err
}
return q.db.GetWorkspaceModulesCreatedAfter(ctx, createdAt)
}
func (q *querier) GetWorkspaceProxies(ctx context.Context) ([]database.WorkspaceProxy, error) {
return fetchWithPostFilter(q.auth, policy.ActionRead, func(ctx context.Context, _ interface{}) ([]database.WorkspaceProxy, error) {
return q.db.GetWorkspaceProxies(ctx)
@@ -3222,6 +3236,13 @@ func (q *querier) InsertWorkspaceBuildParameters(ctx context.Context, arg databa
return q.db.InsertWorkspaceBuildParameters(ctx, arg)
}
func (q *querier) InsertWorkspaceModule(ctx context.Context, arg database.InsertWorkspaceModuleParams) (database.WorkspaceModule, error) {
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {
return database.WorkspaceModule{}, err
}
return q.db.InsertWorkspaceModule(ctx, arg)
}
func (q *querier) InsertWorkspaceProxy(ctx context.Context, arg database.InsertWorkspaceProxyParams) (database.WorkspaceProxy, error) {
return insert(q.log, q.auth, rbac.ResourceWorkspaceProxy, q.db.InsertWorkspaceProxy)(ctx, arg)
}
+15
View File
@@ -2907,6 +2907,21 @@ func (s *MethodTestSuite) TestSystemFunctions() {
}
check.Args(build.ID).Asserts(rbac.ResourceSystem, policy.ActionRead).Returns(rows)
}))
s.Run("InsertWorkspaceModule", s.Subtest(func(db database.Store, check *expects) {
j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{
Type: database.ProvisionerJobTypeWorkspaceBuild,
})
check.Args(database.InsertWorkspaceModuleParams{
JobID: j.ID,
Transition: database.WorkspaceTransitionStart,
}).Asserts(rbac.ResourceSystem, policy.ActionCreate)
}))
s.Run("GetWorkspaceModulesByJobID", s.Subtest(func(db database.Store, check *expects) {
check.Args(uuid.New()).Asserts(rbac.ResourceSystem, policy.ActionRead)
}))
s.Run("GetWorkspaceModulesCreatedAfter", s.Subtest(func(db database.Store, check *expects) {
check.Args(dbtime.Now()).Asserts(rbac.ResourceSystem, policy.ActionRead)
}))
}
func (s *MethodTestSuite) TestNotifications() {
+18
View File
@@ -657,11 +657,29 @@ func WorkspaceResource(t testing.TB, db database.Store, orig database.WorkspaceR
Valid: takeFirst(orig.InstanceType.Valid, false),
},
DailyCost: takeFirst(orig.DailyCost, 0),
ModulePath: sql.NullString{
String: takeFirst(orig.ModulePath.String, ""),
Valid: takeFirst(orig.ModulePath.Valid, true),
},
})
require.NoError(t, err, "insert resource")
return resource
}
func WorkspaceModule(t testing.TB, db database.Store, orig database.WorkspaceModule) database.WorkspaceModule {
module, err := db.InsertWorkspaceModule(genCtx, database.InsertWorkspaceModuleParams{
ID: takeFirst(orig.ID, uuid.New()),
JobID: takeFirst(orig.JobID, uuid.New()),
Transition: takeFirst(orig.Transition, database.WorkspaceTransitionStart),
Source: takeFirst(orig.Source, "test-source"),
Version: takeFirst(orig.Version, "v1.0.0"),
Key: takeFirst(orig.Key, "test-key"),
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
})
require.NoError(t, err, "insert workspace module")
return module
}
func WorkspaceResourceMetadatums(t testing.TB, db database.Store, seed database.WorkspaceResourceMetadatum) []database.WorkspaceResourceMetadatum {
meta, err := db.InsertWorkspaceResourceMetadata(genCtx, database.InsertWorkspaceResourceMetadataParams{
WorkspaceResourceID: takeFirst(seed.WorkspaceResourceID, uuid.New()),
+43
View File
@@ -73,6 +73,7 @@ func New() database.Store {
workspaceAgents: make([]database.WorkspaceAgent, 0),
provisionerJobLogs: make([]database.ProvisionerJobLog, 0),
workspaceResources: make([]database.WorkspaceResource, 0),
workspaceModules: make([]database.WorkspaceModule, 0),
workspaceResourceMetadata: make([]database.WorkspaceResourceMetadatum, 0),
provisionerJobs: make([]database.ProvisionerJob, 0),
templateVersions: make([]database.TemplateVersionTable, 0),
@@ -232,6 +233,7 @@ type data struct {
workspaceBuildParameters []database.WorkspaceBuildParameter
workspaceResourceMetadata []database.WorkspaceResourceMetadatum
workspaceResources []database.WorkspaceResource
workspaceModules []database.WorkspaceModule
workspaces []database.WorkspaceTable
workspaceProxies []database.WorkspaceProxy
customRoles []database.CustomRole
@@ -6671,6 +6673,32 @@ func (q *FakeQuerier) GetWorkspaceByWorkspaceAppID(_ context.Context, workspaceA
return database.Workspace{}, sql.ErrNoRows
}
func (q *FakeQuerier) GetWorkspaceModulesByJobID(_ context.Context, jobID uuid.UUID) ([]database.WorkspaceModule, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
modules := make([]database.WorkspaceModule, 0)
for _, module := range q.workspaceModules {
if module.JobID == jobID {
modules = append(modules, module)
}
}
return modules, nil
}
func (q *FakeQuerier) GetWorkspaceModulesCreatedAfter(_ context.Context, createdAt time.Time) ([]database.WorkspaceModule, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
modules := make([]database.WorkspaceModule, 0)
for _, module := range q.workspaceModules {
if module.CreatedAt.After(createdAt) {
modules = append(modules, module)
}
}
return modules, nil
}
func (q *FakeQuerier) GetWorkspaceProxies(_ context.Context) ([]database.WorkspaceProxy, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
@@ -8233,6 +8261,20 @@ func (q *FakeQuerier) InsertWorkspaceBuildParameters(_ context.Context, arg data
return nil
}
func (q *FakeQuerier) InsertWorkspaceModule(_ context.Context, arg database.InsertWorkspaceModuleParams) (database.WorkspaceModule, error) {
err := validateDatabaseType(arg)
if err != nil {
return database.WorkspaceModule{}, err
}
q.mutex.Lock()
defer q.mutex.Unlock()
workspaceModule := database.WorkspaceModule(arg)
q.workspaceModules = append(q.workspaceModules, workspaceModule)
return workspaceModule, nil
}
func (q *FakeQuerier) InsertWorkspaceProxy(_ context.Context, arg database.InsertWorkspaceProxyParams) (database.WorkspaceProxy, error) {
q.mutex.Lock()
defer q.mutex.Unlock()
@@ -8283,6 +8325,7 @@ func (q *FakeQuerier) InsertWorkspaceResource(_ context.Context, arg database.In
Hide: arg.Hide,
Icon: arg.Icon,
DailyCost: arg.DailyCost,
ModulePath: arg.ModulePath,
}
q.workspaceResources = append(q.workspaceResources, resource)
return resource, nil
+21
View File
@@ -1568,6 +1568,20 @@ func (m queryMetricsStore) GetWorkspaceByWorkspaceAppID(ctx context.Context, wor
return workspace, err
}
func (m queryMetricsStore) GetWorkspaceModulesByJobID(ctx context.Context, jobID uuid.UUID) ([]database.WorkspaceModule, error) {
start := time.Now()
r0, r1 := m.s.GetWorkspaceModulesByJobID(ctx, jobID)
m.queryLatencies.WithLabelValues("GetWorkspaceModulesByJobID").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m queryMetricsStore) GetWorkspaceModulesCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceModule, error) {
start := time.Now()
r0, r1 := m.s.GetWorkspaceModulesCreatedAfter(ctx, createdAt)
m.queryLatencies.WithLabelValues("GetWorkspaceModulesCreatedAfter").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m queryMetricsStore) GetWorkspaceProxies(ctx context.Context) ([]database.WorkspaceProxy, error) {
start := time.Now()
proxies, err := m.s.GetWorkspaceProxies(ctx)
@@ -1995,6 +2009,13 @@ func (m queryMetricsStore) InsertWorkspaceBuildParameters(ctx context.Context, a
return err
}
func (m queryMetricsStore) InsertWorkspaceModule(ctx context.Context, arg database.InsertWorkspaceModuleParams) (database.WorkspaceModule, error) {
start := time.Now()
r0, r1 := m.s.InsertWorkspaceModule(ctx, arg)
m.queryLatencies.WithLabelValues("InsertWorkspaceModule").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m queryMetricsStore) InsertWorkspaceProxy(ctx context.Context, arg database.InsertWorkspaceProxyParams) (database.WorkspaceProxy, error) {
start := time.Now()
proxy, err := m.s.InsertWorkspaceProxy(ctx, arg)
+45
View File
@@ -3307,6 +3307,36 @@ func (mr *MockStoreMockRecorder) GetWorkspaceByWorkspaceAppID(arg0, arg1 any) *g
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceByWorkspaceAppID", reflect.TypeOf((*MockStore)(nil).GetWorkspaceByWorkspaceAppID), arg0, arg1)
}
// GetWorkspaceModulesByJobID mocks base method.
func (m *MockStore) GetWorkspaceModulesByJobID(arg0 context.Context, arg1 uuid.UUID) ([]database.WorkspaceModule, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetWorkspaceModulesByJobID", arg0, arg1)
ret0, _ := ret[0].([]database.WorkspaceModule)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetWorkspaceModulesByJobID indicates an expected call of GetWorkspaceModulesByJobID.
func (mr *MockStoreMockRecorder) GetWorkspaceModulesByJobID(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceModulesByJobID", reflect.TypeOf((*MockStore)(nil).GetWorkspaceModulesByJobID), arg0, arg1)
}
// GetWorkspaceModulesCreatedAfter mocks base method.
func (m *MockStore) GetWorkspaceModulesCreatedAfter(arg0 context.Context, arg1 time.Time) ([]database.WorkspaceModule, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetWorkspaceModulesCreatedAfter", arg0, arg1)
ret0, _ := ret[0].([]database.WorkspaceModule)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetWorkspaceModulesCreatedAfter indicates an expected call of GetWorkspaceModulesCreatedAfter.
func (mr *MockStoreMockRecorder) GetWorkspaceModulesCreatedAfter(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceModulesCreatedAfter", reflect.TypeOf((*MockStore)(nil).GetWorkspaceModulesCreatedAfter), arg0, arg1)
}
// GetWorkspaceProxies mocks base method.
func (m *MockStore) GetWorkspaceProxies(arg0 context.Context) ([]database.WorkspaceProxy, error) {
m.ctrl.T.Helper()
@@ -4224,6 +4254,21 @@ func (mr *MockStoreMockRecorder) InsertWorkspaceBuildParameters(arg0, arg1 any)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceBuildParameters", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceBuildParameters), arg0, arg1)
}
// InsertWorkspaceModule mocks base method.
func (m *MockStore) InsertWorkspaceModule(arg0 context.Context, arg1 database.InsertWorkspaceModuleParams) (database.WorkspaceModule, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "InsertWorkspaceModule", arg0, arg1)
ret0, _ := ret[0].(database.WorkspaceModule)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertWorkspaceModule indicates an expected call of InsertWorkspaceModule.
func (mr *MockStoreMockRecorder) InsertWorkspaceModule(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceModule", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceModule), arg0, arg1)
}
// InsertWorkspaceProxy mocks base method.
func (m *MockStore) InsertWorkspaceProxy(arg0 context.Context, arg1 database.InsertWorkspaceProxyParams) (database.WorkspaceProxy, error) {
m.ctrl.T.Helper()
+17 -1
View File
@@ -1634,6 +1634,16 @@ CREATE VIEW workspace_build_with_user AS
COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.';
CREATE TABLE workspace_modules (
id uuid NOT NULL,
job_id uuid NOT NULL,
transition workspace_transition NOT NULL,
source text NOT NULL,
version text NOT NULL,
key text NOT NULL,
created_at timestamp with time zone NOT NULL
);
CREATE TABLE workspace_proxies (
id uuid NOT NULL,
name text NOT NULL,
@@ -1700,7 +1710,8 @@ CREATE TABLE workspace_resources (
hide boolean DEFAULT false NOT NULL,
icon character varying(256) DEFAULT ''::character varying NOT NULL,
instance_type character varying(256),
daily_cost integer DEFAULT 0 NOT NULL
daily_cost integer DEFAULT 0 NOT NULL,
module_path text
);
CREATE TABLE workspaces (
@@ -2095,6 +2106,8 @@ CREATE INDEX workspace_agents_resource_id_idx ON workspace_agents USING btree (r
CREATE INDEX workspace_app_stats_workspace_id_idx ON workspace_app_stats USING btree (workspace_id);
CREATE INDEX workspace_modules_created_at_idx ON workspace_modules USING btree (created_at);
CREATE UNIQUE INDEX workspace_proxies_lower_name_idx ON workspace_proxies USING btree (lower(name)) WHERE (deleted = false);
CREATE INDEX workspace_resources_job_id_idx ON workspace_resources USING btree (job_id);
@@ -2360,6 +2373,9 @@ ALTER TABLE ONLY workspace_builds
ALTER TABLE ONLY workspace_builds
ADD CONSTRAINT workspace_builds_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY workspace_modules
ADD CONSTRAINT workspace_modules_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ALTER TABLE ONLY workspace_resource_metadata
ADD CONSTRAINT workspace_resource_metadata_workspace_resource_id_fkey FOREIGN KEY (workspace_resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE;
@@ -65,6 +65,7 @@ const (
ForeignKeyWorkspaceBuildsJobID ForeignKeyConstraint = "workspace_builds_job_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ForeignKeyWorkspaceBuildsTemplateVersionID ForeignKeyConstraint = "workspace_builds_template_version_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_template_version_id_fkey FOREIGN KEY (template_version_id) REFERENCES template_versions(id) ON DELETE CASCADE;
ForeignKeyWorkspaceBuildsWorkspaceID ForeignKeyConstraint = "workspace_builds_workspace_id_fkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_fkey FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE;
ForeignKeyWorkspaceModulesJobID ForeignKeyConstraint = "workspace_modules_job_id_fkey" // ALTER TABLE ONLY workspace_modules ADD CONSTRAINT workspace_modules_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ForeignKeyWorkspaceResourceMetadataWorkspaceResourceID ForeignKeyConstraint = "workspace_resource_metadata_workspace_resource_id_fkey" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_workspace_resource_id_fkey FOREIGN KEY (workspace_resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE;
ForeignKeyWorkspaceResourcesJobID ForeignKeyConstraint = "workspace_resources_job_id_fkey" // ALTER TABLE ONLY workspace_resources ADD CONSTRAINT workspace_resources_job_id_fkey FOREIGN KEY (job_id) REFERENCES provisioner_jobs(id) ON DELETE CASCADE;
ForeignKeyWorkspacesOrganizationID ForeignKeyConstraint = "workspaces_organization_id_fkey" // ALTER TABLE ONLY workspaces ADD CONSTRAINT workspaces_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE RESTRICT;
@@ -0,0 +1,5 @@
DROP TABLE workspace_modules;
ALTER TABLE
workspace_resources
DROP COLUMN module_path;
@@ -0,0 +1,16 @@
ALTER TABLE
workspace_resources
ADD
COLUMN module_path TEXT;
CREATE TABLE workspace_modules (
id uuid NOT NULL,
job_id uuid NOT NULL REFERENCES provisioner_jobs (id) ON DELETE CASCADE,
transition workspace_transition NOT NULL,
source TEXT NOT NULL,
version TEXT NOT NULL,
key TEXT NOT NULL,
created_at timestamp with time zone NOT NULL
);
CREATE INDEX workspace_modules_created_at_idx ON workspace_modules (created_at);
@@ -0,0 +1,20 @@
INSERT INTO
public.workspace_modules (
id,
job_id,
transition,
source,
version,
key,
created_at
)
VALUES
(
'5b1a722c-b8a0-40b0-a3a0-d8078fff9f6c',
'424a58cb-61d6-4627-9907-613c396c4a38',
'start',
'test-source',
'v1.0.0',
'test-key',
'2024-11-08 10:00:00+00'
);
+11
View File
@@ -3152,6 +3152,16 @@ type WorkspaceBuildTable struct {
MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"`
}
type WorkspaceModule struct {
ID uuid.UUID `db:"id" json:"id"`
JobID uuid.UUID `db:"job_id" json:"job_id"`
Transition WorkspaceTransition `db:"transition" json:"transition"`
Source string `db:"source" json:"source"`
Version string `db:"version" json:"version"`
Key string `db:"key" json:"key"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
type WorkspaceProxy struct {
ID uuid.UUID `db:"id" json:"id"`
Name string `db:"name" json:"name"`
@@ -3186,6 +3196,7 @@ type WorkspaceResource struct {
Icon string `db:"icon" json:"icon"`
InstanceType sql.NullString `db:"instance_type" json:"instance_type"`
DailyCost int32 `db:"daily_cost" json:"daily_cost"`
ModulePath sql.NullString `db:"module_path" json:"module_path"`
}
type WorkspaceResourceMetadatum struct {
+3
View File
@@ -323,6 +323,8 @@ type sqlcQuerier interface {
GetWorkspaceByID(ctx context.Context, id uuid.UUID) (Workspace, error)
GetWorkspaceByOwnerIDAndName(ctx context.Context, arg GetWorkspaceByOwnerIDAndNameParams) (Workspace, error)
GetWorkspaceByWorkspaceAppID(ctx context.Context, workspaceAppID uuid.UUID) (Workspace, error)
GetWorkspaceModulesByJobID(ctx context.Context, jobID uuid.UUID) ([]WorkspaceModule, error)
GetWorkspaceModulesCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceModule, error)
GetWorkspaceProxies(ctx context.Context) ([]WorkspaceProxy, error)
// Finds a workspace proxy that has an access URL or app hostname that matches
// the provided hostname. This is to check if a hostname matches any workspace
@@ -404,6 +406,7 @@ type sqlcQuerier interface {
InsertWorkspaceAppStats(ctx context.Context, arg InsertWorkspaceAppStatsParams) error
InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) error
InsertWorkspaceBuildParameters(ctx context.Context, arg InsertWorkspaceBuildParametersParams) error
InsertWorkspaceModule(ctx context.Context, arg InsertWorkspaceModuleParams) (WorkspaceModule, error)
InsertWorkspaceProxy(ctx context.Context, arg InsertWorkspaceProxyParams) (WorkspaceProxy, error)
InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error)
InsertWorkspaceResourceMetadata(ctx context.Context, arg InsertWorkspaceResourceMetadataParams) ([]WorkspaceResourceMetadatum, error)
+128 -6
View File
@@ -14115,9 +14115,124 @@ func (q *sqlQuerier) UpdateWorkspaceBuildProvisionerStateByID(ctx context.Contex
return err
}
const getWorkspaceModulesByJobID = `-- name: GetWorkspaceModulesByJobID :many
SELECT
id, job_id, transition, source, version, key, created_at
FROM
workspace_modules
WHERE
job_id = $1
`
func (q *sqlQuerier) GetWorkspaceModulesByJobID(ctx context.Context, jobID uuid.UUID) ([]WorkspaceModule, error) {
rows, err := q.db.QueryContext(ctx, getWorkspaceModulesByJobID, jobID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []WorkspaceModule
for rows.Next() {
var i WorkspaceModule
if err := rows.Scan(
&i.ID,
&i.JobID,
&i.Transition,
&i.Source,
&i.Version,
&i.Key,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getWorkspaceModulesCreatedAfter = `-- name: GetWorkspaceModulesCreatedAfter :many
SELECT id, job_id, transition, source, version, key, created_at FROM workspace_modules WHERE created_at > $1
`
func (q *sqlQuerier) GetWorkspaceModulesCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceModule, error) {
rows, err := q.db.QueryContext(ctx, getWorkspaceModulesCreatedAfter, createdAt)
if err != nil {
return nil, err
}
defer rows.Close()
var items []WorkspaceModule
for rows.Next() {
var i WorkspaceModule
if err := rows.Scan(
&i.ID,
&i.JobID,
&i.Transition,
&i.Source,
&i.Version,
&i.Key,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const insertWorkspaceModule = `-- name: InsertWorkspaceModule :one
INSERT INTO
workspace_modules (id, job_id, transition, source, version, key, created_at)
VALUES
($1, $2, $3, $4, $5, $6, $7) RETURNING id, job_id, transition, source, version, key, created_at
`
type InsertWorkspaceModuleParams struct {
ID uuid.UUID `db:"id" json:"id"`
JobID uuid.UUID `db:"job_id" json:"job_id"`
Transition WorkspaceTransition `db:"transition" json:"transition"`
Source string `db:"source" json:"source"`
Version string `db:"version" json:"version"`
Key string `db:"key" json:"key"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
func (q *sqlQuerier) InsertWorkspaceModule(ctx context.Context, arg InsertWorkspaceModuleParams) (WorkspaceModule, error) {
row := q.db.QueryRowContext(ctx, insertWorkspaceModule,
arg.ID,
arg.JobID,
arg.Transition,
arg.Source,
arg.Version,
arg.Key,
arg.CreatedAt,
)
var i WorkspaceModule
err := row.Scan(
&i.ID,
&i.JobID,
&i.Transition,
&i.Source,
&i.Version,
&i.Key,
&i.CreatedAt,
)
return i, err
}
const getWorkspaceResourceByID = `-- name: GetWorkspaceResourceByID :one
SELECT
id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost
id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost, module_path
FROM
workspace_resources
WHERE
@@ -14138,6 +14253,7 @@ func (q *sqlQuerier) GetWorkspaceResourceByID(ctx context.Context, id uuid.UUID)
&i.Icon,
&i.InstanceType,
&i.DailyCost,
&i.ModulePath,
)
return i, err
}
@@ -14217,7 +14333,7 @@ func (q *sqlQuerier) GetWorkspaceResourceMetadataCreatedAfter(ctx context.Contex
const getWorkspaceResourcesByJobID = `-- name: GetWorkspaceResourcesByJobID :many
SELECT
id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost
id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost, module_path
FROM
workspace_resources
WHERE
@@ -14244,6 +14360,7 @@ func (q *sqlQuerier) GetWorkspaceResourcesByJobID(ctx context.Context, jobID uui
&i.Icon,
&i.InstanceType,
&i.DailyCost,
&i.ModulePath,
); err != nil {
return nil, err
}
@@ -14260,7 +14377,7 @@ func (q *sqlQuerier) GetWorkspaceResourcesByJobID(ctx context.Context, jobID uui
const getWorkspaceResourcesByJobIDs = `-- name: GetWorkspaceResourcesByJobIDs :many
SELECT
id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost
id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost, module_path
FROM
workspace_resources
WHERE
@@ -14287,6 +14404,7 @@ func (q *sqlQuerier) GetWorkspaceResourcesByJobIDs(ctx context.Context, ids []uu
&i.Icon,
&i.InstanceType,
&i.DailyCost,
&i.ModulePath,
); err != nil {
return nil, err
}
@@ -14302,7 +14420,7 @@ func (q *sqlQuerier) GetWorkspaceResourcesByJobIDs(ctx context.Context, ids []uu
}
const getWorkspaceResourcesCreatedAfter = `-- name: GetWorkspaceResourcesCreatedAfter :many
SELECT id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost FROM workspace_resources WHERE created_at > $1
SELECT id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost, module_path FROM workspace_resources WHERE created_at > $1
`
func (q *sqlQuerier) GetWorkspaceResourcesCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceResource, error) {
@@ -14325,6 +14443,7 @@ func (q *sqlQuerier) GetWorkspaceResourcesCreatedAfter(ctx context.Context, crea
&i.Icon,
&i.InstanceType,
&i.DailyCost,
&i.ModulePath,
); err != nil {
return nil, err
}
@@ -14341,9 +14460,9 @@ func (q *sqlQuerier) GetWorkspaceResourcesCreatedAfter(ctx context.Context, crea
const insertWorkspaceResource = `-- name: InsertWorkspaceResource :one
INSERT INTO
workspace_resources (id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost)
workspace_resources (id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost, module_path)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost, module_path
`
type InsertWorkspaceResourceParams struct {
@@ -14357,6 +14476,7 @@ type InsertWorkspaceResourceParams struct {
Icon string `db:"icon" json:"icon"`
InstanceType sql.NullString `db:"instance_type" json:"instance_type"`
DailyCost int32 `db:"daily_cost" json:"daily_cost"`
ModulePath sql.NullString `db:"module_path" json:"module_path"`
}
func (q *sqlQuerier) InsertWorkspaceResource(ctx context.Context, arg InsertWorkspaceResourceParams) (WorkspaceResource, error) {
@@ -14371,6 +14491,7 @@ func (q *sqlQuerier) InsertWorkspaceResource(ctx context.Context, arg InsertWork
arg.Icon,
arg.InstanceType,
arg.DailyCost,
arg.ModulePath,
)
var i WorkspaceResource
err := row.Scan(
@@ -14384,6 +14505,7 @@ func (q *sqlQuerier) InsertWorkspaceResource(ctx context.Context, arg InsertWork
&i.Icon,
&i.InstanceType,
&i.DailyCost,
&i.ModulePath,
)
return i, err
}
@@ -0,0 +1,16 @@
-- name: InsertWorkspaceModule :one
INSERT INTO
workspace_modules (id, job_id, transition, source, version, key, created_at)
VALUES
($1, $2, $3, $4, $5, $6, $7) RETURNING *;
-- name: GetWorkspaceModulesByJobID :many
SELECT
*
FROM
workspace_modules
WHERE
job_id = $1;
-- name: GetWorkspaceModulesCreatedAfter :many
SELECT * FROM workspace_modules WHERE created_at > $1;
@@ -27,9 +27,9 @@ SELECT * FROM workspace_resources WHERE created_at > $1;
-- name: InsertWorkspaceResource :one
INSERT INTO
workspace_resources (id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost)
workspace_resources (id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost, module_path)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *;
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING *;
-- name: GetWorkspaceResourceMetadataByResourceIDs :many
SELECT
@@ -1261,12 +1261,28 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob)
slog.F("resource_type", resource.Type),
slog.F("transition", transition))
err = InsertWorkspaceResource(ctx, s.Database, jobID, transition, resource, telemetrySnapshot)
if err != nil {
if err := InsertWorkspaceResource(ctx, s.Database, jobID, transition, resource, telemetrySnapshot); err != nil {
return nil, xerrors.Errorf("insert resource: %w", err)
}
}
}
for transition, modules := range map[database.WorkspaceTransition][]*sdkproto.Module{
database.WorkspaceTransitionStart: jobType.TemplateImport.StartModules,
database.WorkspaceTransitionStop: jobType.TemplateImport.StopModules,
} {
for _, module := range modules {
s.Logger.Info(ctx, "inserting template import job module",
slog.F("job_id", job.ID.String()),
slog.F("module_source", module.Source),
slog.F("module_version", module.Version),
slog.F("module_key", module.Key),
slog.F("transition", transition))
if err := InsertWorkspaceModule(ctx, s.Database, jobID, transition, module, telemetrySnapshot); err != nil {
return nil, xerrors.Errorf("insert module: %w", err)
}
}
}
for _, richParameter := range jobType.TemplateImport.RichParameters {
s.Logger.Info(ctx, "inserting template import job parameter",
@@ -1472,6 +1488,11 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob)
return xerrors.Errorf("insert provisioner job: %w", err)
}
}
for _, module := range jobType.WorkspaceBuild.Modules {
if err := InsertWorkspaceModule(ctx, db, job.ID, workspaceBuild.Transition, module, telemetrySnapshot); err != nil {
return xerrors.Errorf("insert provisioner job module: %w", err)
}
}
// On start, we want to ensure that workspace agents timeout statuses
// are propagated. This method is simple and does not protect against
@@ -1653,6 +1674,16 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob)
return nil, xerrors.Errorf("insert resource: %w", err)
}
}
for _, module := range jobType.TemplateDryRun.Modules {
s.Logger.Info(ctx, "inserting template dry-run job module",
slog.F("job_id", job.ID.String()),
slog.F("module_source", module.Source),
)
if err := InsertWorkspaceModule(ctx, s.Database, jobID, database.WorkspaceTransitionStart, module, telemetrySnapshot); err != nil {
return nil, xerrors.Errorf("insert module: %w", err)
}
}
err = s.Database.UpdateProvisionerJobWithCompleteByID(ctx, database.UpdateProvisionerJobWithCompleteByIDParams{
ID: jobID,
@@ -1734,6 +1765,23 @@ func (s *server) startTrace(ctx context.Context, name string, opts ...trace.Span
))...)
}
func InsertWorkspaceModule(ctx context.Context, db database.Store, jobID uuid.UUID, transition database.WorkspaceTransition, protoModule *sdkproto.Module, snapshot *telemetry.Snapshot) error {
module, err := db.InsertWorkspaceModule(ctx, database.InsertWorkspaceModuleParams{
ID: uuid.New(),
CreatedAt: dbtime.Now(),
JobID: jobID,
Transition: transition,
Source: protoModule.Source,
Version: protoModule.Version,
Key: protoModule.Key,
})
if err != nil {
return xerrors.Errorf("insert provisioner job module %q: %w", protoModule.Source, err)
}
snapshot.WorkspaceModules = append(snapshot.WorkspaceModules, telemetry.ConvertWorkspaceModule(module))
return nil
}
func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.UUID, transition database.WorkspaceTransition, protoResource *sdkproto.Resource, snapshot *telemetry.Snapshot) error {
resource, err := db.InsertWorkspaceResource(ctx, database.InsertWorkspaceResourceParams{
ID: uuid.New(),
@@ -1749,6 +1797,11 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
String: protoResource.InstanceType,
Valid: protoResource.InstanceType != "",
},
ModulePath: sql.NullString{
String: protoResource.ModulePath,
// empty string is root module
Valid: true,
},
})
if err != nil {
return xerrors.Errorf("insert provisioner job resource %q: %w", protoResource.Name, err)
@@ -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) {
+55 -1
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"`
+30
View File
@@ -5,6 +5,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"sort"
"testing"
"time"
@@ -20,6 +21,7 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbmem"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/telemetry"
"github.com/coder/coder/v2/testutil"
@@ -87,6 +89,8 @@ func TestTelemetry(t *testing.T) {
assert.NoError(t, err)
_, _ = dbgen.WorkspaceProxy(t, db, database.WorkspaceProxy{})
_ = dbgen.WorkspaceModule(t, db, database.WorkspaceModule{})
_, snapshot := collectSnapshot(t, db, nil)
require.Len(t, snapshot.ProvisionerJobs, 1)
require.Len(t, snapshot.Licenses, 1)
@@ -103,6 +107,7 @@ func TestTelemetry(t *testing.T) {
require.Len(t, snapshot.WorkspaceResources, 1)
require.Len(t, snapshot.WorkspaceAgentStats, 1)
require.Len(t, snapshot.WorkspaceProxies, 1)
require.Len(t, snapshot.WorkspaceModules, 1)
wsa := snapshot.WorkspaceAgents[0]
require.Len(t, wsa.Subsystems, 2)
@@ -119,6 +124,31 @@ func TestTelemetry(t *testing.T) {
require.Len(t, snapshot.Users, 1)
require.Equal(t, snapshot.Users[0].EmailHashed, "bb44bf07cf9a2db0554bba63a03d822c927deae77df101874496df5a6a3e896d@coder.com")
})
t.Run("HashedModule", func(t *testing.T) {
t.Parallel()
db, _ := dbtestutil.NewDB(t)
pj := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{})
_ = dbgen.WorkspaceModule(t, db, database.WorkspaceModule{
JobID: pj.ID,
Source: "registry.coder.com/terraform/aws",
Version: "1.0.0",
})
_ = dbgen.WorkspaceModule(t, db, database.WorkspaceModule{
JobID: pj.ID,
Source: "internal-url.com/some-module",
Version: "1.0.0",
})
_, snapshot := collectSnapshot(t, db, nil)
require.Len(t, snapshot.WorkspaceModules, 2)
modules := snapshot.WorkspaceModules
sort.Slice(modules, func(i, j int) bool {
return modules[i].Source < modules[j].Source
})
require.Equal(t, modules[0].Source, "921c61d6f3eef5118f3cae658d1518b378c5b02a4955a766c791440894d989c5")
require.Equal(t, modules[0].Version, "92521fc3cbd964bdc9f584a991b89fddaa5754ed1cc96d6d42445338669c1305")
require.Equal(t, modules[1].Source, "registry.coder.com/terraform/aws")
require.Equal(t, modules[1].Version, "1.0.0")
})
}
// nolint:paralleltest
+1
View File
@@ -344,6 +344,7 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-terraform-address v0.0.0-20240523040243-ccea9d309e0c
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
github.com/hashicorp/hcl/v2 v2.22.0
+2
View File
@@ -562,6 +562,8 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-terraform-address v0.0.0-20240523040243-ccea9d309e0c h1:5v6L/m/HcAZYbrLGYBpPkcCVtDWwIgFxq2+FUmfPxPk=
github.com/hashicorp/go-terraform-address v0.0.0-20240523040243-ccea9d309e0c/go.mod h1:xoy1vl2+4YvqSQEkKcFjNYxTk7cll+o1f1t2wxnHIX8=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
+3 -3
View File
@@ -356,7 +356,7 @@ func (e *executor) planResources(ctx, killCtx context.Context, planfilePath stri
}
modules = append(modules, plan.PlannedValues.RootModule)
state, err := ConvertState(modules, rawGraph)
state, err := ConvertState(ctx, modules, rawGraph, e.server.logger)
if err != nil {
return nil, err
}
@@ -484,9 +484,9 @@ func (e *executor) stateResources(ctx, killCtx context.Context) (*State, error)
return converted, nil
}
converted, err = ConvertState([]*tfjson.StateModule{
converted, err = ConvertState(ctx, []*tfjson.StateModule{
state.Values.RootModule,
}, rawGraph)
}, rawGraph, e.server.logger)
if err != nil {
return nil, err
}
+64
View File
@@ -0,0 +1,64 @@
package terraform
import (
"encoding/json"
"os"
"path/filepath"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/provisionersdk/proto"
)
type module struct {
Source string `json:"Source"`
Version string `json:"Version"`
Key string `json:"Key"`
}
type modulesFile struct {
Modules []*module `json:"Modules"`
}
func getModulesFilePath(workdir string) string {
return filepath.Join(workdir, ".terraform", "modules", "modules.json")
}
func parseModulesFile(filePath string) ([]*proto.Module, error) {
modules := &modulesFile{}
data, err := os.ReadFile(filePath)
if err != nil {
return nil, xerrors.Errorf("read modules file: %w", err)
}
if err := json.Unmarshal(data, modules); err != nil {
return nil, xerrors.Errorf("unmarshal modules file: %w", err)
}
protoModules := make([]*proto.Module, len(modules.Modules))
for i, m := range modules.Modules {
protoModules[i] = &proto.Module{Source: m.Source, Version: m.Version, Key: m.Key}
}
return protoModules, nil
}
// getModules returns the modules from the modules file if it exists.
// It returns nil if the file does not exist.
// Modules become available after terraform init.
func getModules(workdir string) ([]*proto.Module, error) {
filePath := getModulesFilePath(workdir)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return nil, nil
}
modules, err := parseModulesFile(filePath)
if err != nil {
return nil, xerrors.Errorf("parse modules file: %w", err)
}
filteredModules := []*proto.Module{}
for _, m := range modules {
// Empty string means root module. It's always present, so we skip it.
if m.Source == "" {
continue
}
filteredModules = append(filteredModules, m)
}
return filteredModules, nil
}
+8
View File
@@ -142,6 +142,13 @@ func (s *server) Plan(
return provisionersdk.PlanErrorf("initialize terraform: %s", err)
}
modules, err := getModules(sess.WorkDirectory)
if err != nil {
// We allow getModules to fail, as the result is used only
// for telemetry purposes now.
s.logger.Error(ctx, "failed to get modules from disk", slog.Error(err))
}
initTimings.ingest(createInitTimingsEvent(timingInitComplete))
s.logger.Debug(ctx, "ran initialization")
@@ -167,6 +174,7 @@ func (s *server) Plan(
// Prepend init timings since they occur prior to plan timings.
// Order is irrelevant; this is merely indicative.
resp.Timings = append(initTimings.aggregate(), resp.Timings...)
resp.Modules = modules
return resp
}
+80 -1
View File
@@ -82,7 +82,21 @@ func makeTar(t *testing.T, files map[string]string) []byte {
t.Helper()
var buffer bytes.Buffer
writer := tar.NewWriter(&buffer)
addedDirs := make(map[string]bool)
for name, content := range files {
// Add parent directories if they don't exist
dir := filepath.Dir(name)
if dir != "." && !addedDirs[dir] {
err := writer.WriteHeader(&tar.Header{
Name: dir + "/", // Directory names must end with /
Mode: 0o755,
Typeflag: tar.TypeDir,
})
require.NoError(t, err)
addedDirs[dir] = true
}
err := writer.WriteHeader(&tar.Header{
Name: name,
Size: int64(len(content)),
@@ -745,6 +759,45 @@ func TestProvision(t *testing.T) {
}},
},
},
{
Name: "returns-modules",
Files: map[string]string{
"main.tf": `module "hello" {
source = "./module"
}`,
"module/module.tf": `
resource "null_resource" "example" {}
module "there" {
source = "./inner_module"
}
`,
"module/inner_module/inner_module.tf": `
resource "null_resource" "inner_example" {}
`,
},
Request: &proto.PlanRequest{},
Response: &proto.PlanComplete{
Resources: []*proto.Resource{{
Name: "example",
Type: "null_resource",
ModulePath: "module.hello",
}, {
Name: "inner_example",
Type: "null_resource",
ModulePath: "module.hello.module.there",
}},
Modules: []*proto.Module{{
Key: "hello",
Version: "",
Source: "./module",
}, {
Key: "hello.there",
Version: "",
Source: "./inner_module",
}},
},
},
}
for _, testCase := range testCases {
@@ -799,7 +852,7 @@ func TestProvision(t *testing.T) {
if testCase.Response != nil {
require.Equal(t, testCase.Response.Error, planComplete.Error)
// Remove randomly generated data.
// Remove randomly generated data and sort by name.
normalizeResources(planComplete.Resources)
resourcesGot, err := json.Marshal(planComplete.Resources)
require.NoError(t, err)
@@ -812,6 +865,12 @@ func TestProvision(t *testing.T) {
parametersWant, err := json.Marshal(testCase.Response.Parameters)
require.NoError(t, err)
require.Equal(t, string(parametersWant), string(parametersGot))
modulesGot, err := json.Marshal(planComplete.Modules)
require.NoError(t, err)
modulesWant, err := json.Marshal(testCase.Response.Modules)
require.NoError(t, err)
require.Equal(t, string(modulesWant), string(modulesGot))
}
if testCase.Apply {
@@ -852,6 +911,9 @@ func normalizeResources(resources []*proto.Resource) {
agent.Auth = &proto.Agent_Token{}
}
}
sort.Slice(resources, func(i, j int) bool {
return resources[i].Name < resources[j].Name
})
}
// nolint:paralleltest
@@ -929,3 +991,20 @@ func TestProvision_SafeEnv(t *testing.T) {
require.NotContains(t, log, secretValue)
require.Contains(t, log, "CODER_")
}
func TestProvision_MalformedModules(t *testing.T) {
t.Parallel()
ctx, api := setupProvisioner(t, nil)
sess := configure(ctx, t, api, &proto.Config{
TemplateSourceArchive: makeTar(t, map[string]string{
"main.tf": `module "hello" { source = "./module" }`,
"module/module.tf": `resource "null_`,
}),
})
err := sendPlan(sess, proto.WorkspaceTransition_START)
require.NoError(t, err)
log := readProvisionLog(t, sess)
require.Contains(t, log, "Invalid block definition")
}
+37 -1
View File
@@ -1,6 +1,7 @@
package terraform
import (
"context"
"fmt"
"strings"
@@ -9,8 +10,12 @@ import (
"github.com/mitchellh/mapstructure"
"golang.org/x/xerrors"
"cdr.dev/slog"
"github.com/coder/terraform-provider-coder/provider"
tfaddr "github.com/hashicorp/go-terraform-address"
"github.com/coder/coder/v2/coderd/util/slice"
stringutil "github.com/coder/coder/v2/coderd/util/strings"
"github.com/coder/coder/v2/codersdk"
@@ -129,10 +134,12 @@ type State struct {
ExternalAuthProviders []*proto.ExternalAuthProviderResource
}
var ErrInvalidTerraformAddr = xerrors.New("invalid terraform address")
// ConvertState consumes Terraform state and a GraphViz representation
// produced by `terraform graph` to produce resources consumable by Coder.
// nolint:gocognit // This function makes more sense being large for now, until refactored.
func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error) {
func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph string, logger slog.Logger) (*State, error) {
parsedGraph, err := gographviz.ParseString(rawGraph)
if err != nil {
return nil, xerrors.Errorf("parse graph: %w", err)
@@ -593,6 +600,20 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error
continue
}
label := convertAddressToLabel(resource.Address)
modulePath, err := convertAddressToModulePath(resource.Address)
if err != nil {
// Module path recording was added primarily to keep track of
// modules in telemetry. We're adding this sentinel value so
// we can detect if there are any issues with the address
// parsing.
//
// We don't want to set modulePath to null here because, in
// the database, a null value in WorkspaceResource's ModulePath
// indicates "this resource was created before module paths
// were tracked."
modulePath = fmt.Sprintf("%s", ErrInvalidTerraformAddr)
logger.Error(ctx, "failed to parse Terraform address", slog.F("address", resource.Address))
}
agents, exists := resourceAgents[label]
if exists {
@@ -608,6 +629,7 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error
Icon: resourceIcon[label],
DailyCost: resourceCost[label],
InstanceType: applyInstanceType(resource),
ModulePath: modulePath,
})
}
}
@@ -752,6 +774,20 @@ func convertAddressToLabel(address string) string {
return cut
}
// convertAddressToModulePath returns the module path from a Terraform address.
// eg. "module.ec2_dev.ec2_instance.dev[0]" becomes "module.ec2_dev".
// Empty string is returned for the root module.
//
// Module paths are defined in the Terraform spec:
// https://github.com/hashicorp/terraform/blob/ef071f3d0e49ba421ae931c65b263827a8af1adb/website/docs/internals/resource-addressing.html.markdown#module-path
func convertAddressToModulePath(address string) (string, error) {
addr, err := tfaddr.NewAddress(address)
if err != nil {
return "", xerrors.Errorf("parse terraform address: %w", err)
}
return addr.ModulePath.String(), nil
}
func dependsOnAgent(graph *gographviz.Graph, agent *proto.Agent, resourceAgentID string, resource *tfjson.StateResource) bool {
// Plan: we need to find if there is edge between the agent and the resource.
if agent.Id == "" && resourceAgentID == "" {
+46 -12
View File
@@ -1,6 +1,7 @@
package terraform_test
import (
"context"
"encoding/json"
"fmt"
"os"
@@ -14,11 +15,18 @@ import (
"github.com/stretchr/testify/require"
protobuf "google.golang.org/protobuf/proto"
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/cryptorand"
"github.com/coder/coder/v2/provisioner/terraform"
"github.com/coder/coder/v2/provisionersdk/proto"
)
func ctxAndLogger(t *testing.T) (context.Context, slog.Logger) {
return context.Background(), slogtest.Make(t, nil).Leveled(slog.LevelDebug)
}
func TestConvertResources(t *testing.T) {
t.Parallel()
// nolint:dogsled
@@ -109,6 +117,7 @@ func TestConvertResources(t *testing.T) {
ConnectionTimeoutSeconds: 120,
DisplayApps: &displayApps,
}},
ModulePath: "module.module",
}},
},
// Ensures the attachment of multiple agents to a single
@@ -687,6 +696,7 @@ func TestConvertResources(t *testing.T) {
dir := filepath.Join(filepath.Dir(filename), "testdata", folderName)
t.Run("Plan", func(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)
tfPlanRaw, err := os.ReadFile(filepath.Join(dir, folderName+".tfplan.json"))
require.NoError(t, err)
@@ -704,7 +714,7 @@ func TestConvertResources(t *testing.T) {
// and that no errors occur!
modules = append(modules, tfPlan.PlannedValues.RootModule)
}
state, err := terraform.ConvertState(modules, string(tfPlanGraph))
state, err := terraform.ConvertState(ctx, modules, string(tfPlanGraph), logger)
require.NoError(t, err)
sortResources(state.Resources)
sortExternalAuthProviders(state.ExternalAuthProviders)
@@ -762,6 +772,7 @@ func TestConvertResources(t *testing.T) {
t.Run("Provision", func(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)
tfStateRaw, err := os.ReadFile(filepath.Join(dir, folderName+".tfstate.json"))
require.NoError(t, err)
var tfState tfjson.State
@@ -770,7 +781,7 @@ func TestConvertResources(t *testing.T) {
tfStateGraph, err := os.ReadFile(filepath.Join(dir, folderName+".tfstate.dot"))
require.NoError(t, err)
state, err := terraform.ConvertState([]*tfjson.StateModule{tfState.Values.RootModule}, string(tfStateGraph))
state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{tfState.Values.RootModule}, string(tfStateGraph), logger)
require.NoError(t, err)
sortResources(state.Resources)
sortExternalAuthProviders(state.ExternalAuthProviders)
@@ -805,8 +816,27 @@ func TestConvertResources(t *testing.T) {
}
}
func TestInvalidTerraformAddress(t *testing.T) {
t.Parallel()
ctx, logger := context.Background(), slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{{
Resources: []*tfjson.StateResource{{
Address: "invalid",
Type: "invalid",
Name: "invalid",
Mode: tfjson.ManagedResourceMode,
AttributeValues: map[string]interface{}{},
}},
}}, `digraph {}`, logger)
require.Nil(t, err)
require.Len(t, state.Resources, 1)
require.Equal(t, state.Resources[0].Name, "invalid")
require.Equal(t, state.Resources[0].ModulePath, "invalid terraform address")
}
func TestAppSlugValidation(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)
// nolint:dogsled
_, filename, _, _ := runtime.Caller(0)
@@ -828,7 +858,7 @@ func TestAppSlugValidation(t *testing.T) {
}
}
state, err := terraform.ConvertState([]*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph))
state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph), logger)
require.Nil(t, state)
require.Error(t, err)
require.ErrorContains(t, err, "invalid app slug")
@@ -840,7 +870,7 @@ func TestAppSlugValidation(t *testing.T) {
}
}
state, err = terraform.ConvertState([]*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph))
state, err = terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph), logger)
require.Nil(t, state)
require.Error(t, err)
require.ErrorContains(t, err, "duplicate app slug")
@@ -848,6 +878,7 @@ func TestAppSlugValidation(t *testing.T) {
func TestMetadataResourceDuplicate(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)
// Load the multiple-apps state file and edit it.
dir := filepath.Join("testdata", "resource-metadata-duplicate")
@@ -859,7 +890,7 @@ func TestMetadataResourceDuplicate(t *testing.T) {
tfPlanGraph, err := os.ReadFile(filepath.Join(dir, "resource-metadata-duplicate.tfplan.dot"))
require.NoError(t, err)
state, err := terraform.ConvertState([]*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph))
state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph), logger)
require.Nil(t, state)
require.Error(t, err)
require.ErrorContains(t, err, "duplicate metadata resource: null_resource.about")
@@ -867,6 +898,7 @@ func TestMetadataResourceDuplicate(t *testing.T) {
func TestParameterValidation(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)
// nolint:dogsled
_, filename, _, _ := runtime.Caller(0)
@@ -890,7 +922,7 @@ func TestParameterValidation(t *testing.T) {
}
}
state, err := terraform.ConvertState([]*tfjson.StateModule{tfPlan.PriorState.Values.RootModule}, string(tfPlanGraph))
state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PriorState.Values.RootModule}, string(tfPlanGraph), logger)
require.Nil(t, state)
require.Error(t, err)
require.ErrorContains(t, err, "coder_parameter names must be unique but \"identical\" appears multiple times")
@@ -906,7 +938,7 @@ func TestParameterValidation(t *testing.T) {
}
}
state, err = terraform.ConvertState([]*tfjson.StateModule{tfPlan.PriorState.Values.RootModule}, string(tfPlanGraph))
state, err = terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PriorState.Values.RootModule}, string(tfPlanGraph), logger)
require.Nil(t, state)
require.Error(t, err)
require.ErrorContains(t, err, "coder_parameter names must be unique but \"identical-0\" and \"identical-1\" appear multiple times")
@@ -922,7 +954,7 @@ func TestParameterValidation(t *testing.T) {
}
}
state, err = terraform.ConvertState([]*tfjson.StateModule{tfPlan.PriorState.Values.RootModule}, string(tfPlanGraph))
state, err = terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PriorState.Values.RootModule}, string(tfPlanGraph), logger)
require.Nil(t, state)
require.Error(t, err)
require.ErrorContains(t, err, "coder_parameter names must be unique but \"identical-0\", \"identical-1\" and \"identical-2\" appear multiple times")
@@ -953,9 +985,10 @@ func TestInstanceTypeAssociation(t *testing.T) {
tc := tc
t.Run(tc.ResourceType, func(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)
instanceType, err := cryptorand.String(12)
require.NoError(t, err)
state, err := terraform.ConvertState([]*tfjson.StateModule{{
state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{{
Resources: []*tfjson.StateResource{{
Address: tc.ResourceType + ".dev",
Type: tc.ResourceType,
@@ -972,7 +1005,7 @@ func TestInstanceTypeAssociation(t *testing.T) {
subgraph "root" {
"[root] `+tc.ResourceType+`.dev" [label = "`+tc.ResourceType+`.dev", shape = "box"]
}
}`)
}`, logger)
require.NoError(t, err)
require.Len(t, state.Resources, 1)
require.Equal(t, state.Resources[0].GetInstanceType(), instanceType)
@@ -1011,9 +1044,10 @@ func TestInstanceIDAssociation(t *testing.T) {
tc := tc
t.Run(tc.ResourceType, func(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)
instanceID, err := cryptorand.String(12)
require.NoError(t, err)
state, err := terraform.ConvertState([]*tfjson.StateModule{{
state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{{
Resources: []*tfjson.StateResource{{
Address: "coder_agent.dev",
Type: "coder_agent",
@@ -1043,7 +1077,7 @@ func TestInstanceIDAssociation(t *testing.T) {
"[root] `+tc.ResourceType+`.dev" -> "[root] coder_agent.dev"
}
}
`)
`, logger)
require.NoError(t, err)
require.Len(t, state.Resources, 1)
require.Len(t, state.Resources[0].Agents, 1)
+104 -54
View File
@@ -1215,6 +1215,7 @@ type CompletedJob_WorkspaceBuild struct {
State []byte `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"`
Resources []*proto.Resource `protobuf:"bytes,2,rep,name=resources,proto3" json:"resources,omitempty"`
Timings []*proto.Timing `protobuf:"bytes,3,rep,name=timings,proto3" json:"timings,omitempty"`
Modules []*proto.Module `protobuf:"bytes,4,rep,name=modules,proto3" json:"modules,omitempty"`
}
func (x *CompletedJob_WorkspaceBuild) Reset() {
@@ -1270,6 +1271,13 @@ func (x *CompletedJob_WorkspaceBuild) GetTimings() []*proto.Timing {
return nil
}
func (x *CompletedJob_WorkspaceBuild) GetModules() []*proto.Module {
if x != nil {
return x.Modules
}
return nil
}
type CompletedJob_TemplateImport struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -1280,6 +1288,8 @@ type CompletedJob_TemplateImport struct {
RichParameters []*proto.RichParameter `protobuf:"bytes,3,rep,name=rich_parameters,json=richParameters,proto3" json:"rich_parameters,omitempty"`
ExternalAuthProvidersNames []string `protobuf:"bytes,4,rep,name=external_auth_providers_names,json=externalAuthProvidersNames,proto3" json:"external_auth_providers_names,omitempty"`
ExternalAuthProviders []*proto.ExternalAuthProviderResource `protobuf:"bytes,5,rep,name=external_auth_providers,json=externalAuthProviders,proto3" json:"external_auth_providers,omitempty"`
StartModules []*proto.Module `protobuf:"bytes,6,rep,name=start_modules,json=startModules,proto3" json:"start_modules,omitempty"`
StopModules []*proto.Module `protobuf:"bytes,7,rep,name=stop_modules,json=stopModules,proto3" json:"stop_modules,omitempty"`
}
func (x *CompletedJob_TemplateImport) Reset() {
@@ -1349,12 +1359,27 @@ func (x *CompletedJob_TemplateImport) GetExternalAuthProviders() []*proto.Extern
return nil
}
func (x *CompletedJob_TemplateImport) GetStartModules() []*proto.Module {
if x != nil {
return x.StartModules
}
return nil
}
func (x *CompletedJob_TemplateImport) GetStopModules() []*proto.Module {
if x != nil {
return x.StopModules
}
return nil
}
type CompletedJob_TemplateDryRun struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Resources []*proto.Resource `protobuf:"bytes,1,rep,name=resources,proto3" json:"resources,omitempty"`
Modules []*proto.Module `protobuf:"bytes,2,rep,name=modules,proto3" json:"modules,omitempty"`
}
func (x *CompletedJob_TemplateDryRun) Reset() {
@@ -1396,6 +1421,13 @@ func (x *CompletedJob_TemplateDryRun) GetResources() []*proto.Resource {
return nil
}
func (x *CompletedJob_TemplateDryRun) GetModules() []*proto.Module {
if x != nil {
return x.Modules
}
return nil
}
var File_provisionerd_proto_provisionerd_proto protoreflect.FileDescriptor
var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
@@ -1524,7 +1556,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73,
0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x49, 0x6d, 0x70, 0x6f,
0x72, 0x74, 0x1a, 0x10, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72,
0x79, 0x52, 0x75, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x80, 0x07, 0x0a,
0x79, 0x52, 0x75, 0x6e, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd0, 0x08, 0x0a,
0x0c, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x12, 0x15, 0x0a,
0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a,
0x6f, 0x62, 0x49, 0x64, 0x12, 0x54, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
@@ -1543,7 +1575,7 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65,
0x74, 0x65, 0x64, 0x4a, 0x6f, 0x62, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44,
0x72, 0x79, 0x52, 0x75, 0x6e, 0x48, 0x00, 0x52, 0x0e, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0x8a, 0x01, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b,
0x65, 0x44, 0x72, 0x79, 0x52, 0x75, 0x6e, 0x1a, 0xb9, 0x01, 0x0a, 0x0e, 0x57, 0x6f, 0x72, 0x6b,
0x73, 0x70, 0x61, 0x63, 0x65, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74,
0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65,
0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20,
@@ -1552,35 +1584,48 @@ var file_provisionerd_proto_provisionerd_proto_rawDesc = []byte{
0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73,
0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x74, 0x69, 0x6d,
0x69, 0x6e, 0x67, 0x73, 0x1a, 0xf9, 0x02, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74,
0x65, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74,
0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, 0x5f,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x69, 0x6e, 0x67, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18,
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75,
0x6c, 0x65, 0x73, 0x1a, 0xeb, 0x03, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65,
0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f,
0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61,
0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63,
0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, 0x69, 0x63, 0x68,
0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x1d, 0x65, 0x78,
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28,
0x09, 0x52, 0x1a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x61, 0x0a,
0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70,
0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
0x72, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73,
0x1a, 0x45, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79, 0x52,
0x75, 0x6e, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18,
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f,
0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0e, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15,
0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0d, 0x73, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72,
0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68,
0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0e, 0x72, 0x69, 0x63, 0x68, 0x50,
0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, 0x41, 0x0a, 0x1d, 0x65, 0x78, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x64, 0x65, 0x72, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09,
0x52, 0x1a, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72,
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x17,
0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e,
0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x45, 0x78, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x15, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12,
0x38, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x0c, 0x73, 0x74, 0x61,
0x72, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x0c, 0x73, 0x74, 0x6f,
0x70, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f,
0x64, 0x75, 0x6c, 0x65, 0x52, 0x0b, 0x73, 0x74, 0x6f, 0x70, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
0x73, 0x1a, 0x74, 0x0a, 0x0e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x44, 0x72, 0x79,
0x52, 0x75, 0x6e, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69,
0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75,
0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76,
0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07,
0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22,
0xb0, 0x01, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73,
0x69, 0x6f, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
@@ -1722,8 +1767,9 @@ var file_provisionerd_proto_provisionerd_proto_goTypes = []interface{}{
(*proto.Metadata)(nil), // 27: provisioner.Metadata
(*proto.Timing)(nil), // 28: provisioner.Timing
(*proto.Resource)(nil), // 29: provisioner.Resource
(*proto.RichParameter)(nil), // 30: provisioner.RichParameter
(*proto.ExternalAuthProviderResource)(nil), // 31: provisioner.ExternalAuthProviderResource
(*proto.Module)(nil), // 30: provisioner.Module
(*proto.RichParameter)(nil), // 31: provisioner.RichParameter
(*proto.ExternalAuthProviderResource)(nil), // 32: provisioner.ExternalAuthProviderResource
}
var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{
11, // 0: provisionerd.AcquiredJob.workspace_build:type_name -> provisionerd.AcquiredJob.WorkspaceBuild
@@ -1755,28 +1801,32 @@ var file_provisionerd_proto_provisionerd_proto_depIdxs = []int32{
28, // 26: provisionerd.FailedJob.WorkspaceBuild.timings:type_name -> provisioner.Timing
29, // 27: provisionerd.CompletedJob.WorkspaceBuild.resources:type_name -> provisioner.Resource
28, // 28: provisionerd.CompletedJob.WorkspaceBuild.timings:type_name -> provisioner.Timing
29, // 29: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource
29, // 30: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource
30, // 31: provisionerd.CompletedJob.TemplateImport.rich_parameters:type_name -> provisioner.RichParameter
31, // 32: provisionerd.CompletedJob.TemplateImport.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
29, // 33: provisionerd.CompletedJob.TemplateDryRun.resources:type_name -> provisioner.Resource
1, // 34: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty
10, // 35: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:input_type -> provisionerd.CancelAcquire
8, // 36: provisionerd.ProvisionerDaemon.CommitQuota:input_type -> provisionerd.CommitQuotaRequest
6, // 37: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest
3, // 38: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob
4, // 39: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob
2, // 40: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob
2, // 41: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:output_type -> provisionerd.AcquiredJob
9, // 42: provisionerd.ProvisionerDaemon.CommitQuota:output_type -> provisionerd.CommitQuotaResponse
7, // 43: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse
1, // 44: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty
1, // 45: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty
40, // [40:46] is the sub-list for method output_type
34, // [34:40] is the sub-list for method input_type
34, // [34:34] is the sub-list for extension type_name
34, // [34:34] is the sub-list for extension extendee
0, // [0:34] is the sub-list for field type_name
30, // 29: provisionerd.CompletedJob.WorkspaceBuild.modules:type_name -> provisioner.Module
29, // 30: provisionerd.CompletedJob.TemplateImport.start_resources:type_name -> provisioner.Resource
29, // 31: provisionerd.CompletedJob.TemplateImport.stop_resources:type_name -> provisioner.Resource
31, // 32: provisionerd.CompletedJob.TemplateImport.rich_parameters:type_name -> provisioner.RichParameter
32, // 33: provisionerd.CompletedJob.TemplateImport.external_auth_providers:type_name -> provisioner.ExternalAuthProviderResource
30, // 34: provisionerd.CompletedJob.TemplateImport.start_modules:type_name -> provisioner.Module
30, // 35: provisionerd.CompletedJob.TemplateImport.stop_modules:type_name -> provisioner.Module
29, // 36: provisionerd.CompletedJob.TemplateDryRun.resources:type_name -> provisioner.Resource
30, // 37: provisionerd.CompletedJob.TemplateDryRun.modules:type_name -> provisioner.Module
1, // 38: provisionerd.ProvisionerDaemon.AcquireJob:input_type -> provisionerd.Empty
10, // 39: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:input_type -> provisionerd.CancelAcquire
8, // 40: provisionerd.ProvisionerDaemon.CommitQuota:input_type -> provisionerd.CommitQuotaRequest
6, // 41: provisionerd.ProvisionerDaemon.UpdateJob:input_type -> provisionerd.UpdateJobRequest
3, // 42: provisionerd.ProvisionerDaemon.FailJob:input_type -> provisionerd.FailedJob
4, // 43: provisionerd.ProvisionerDaemon.CompleteJob:input_type -> provisionerd.CompletedJob
2, // 44: provisionerd.ProvisionerDaemon.AcquireJob:output_type -> provisionerd.AcquiredJob
2, // 45: provisionerd.ProvisionerDaemon.AcquireJobWithCancel:output_type -> provisionerd.AcquiredJob
9, // 46: provisionerd.ProvisionerDaemon.CommitQuota:output_type -> provisionerd.CommitQuotaResponse
7, // 47: provisionerd.ProvisionerDaemon.UpdateJob:output_type -> provisionerd.UpdateJobResponse
1, // 48: provisionerd.ProvisionerDaemon.FailJob:output_type -> provisionerd.Empty
1, // 49: provisionerd.ProvisionerDaemon.CompleteJob:output_type -> provisionerd.Empty
44, // [44:50] is the sub-list for method output_type
38, // [38:44] is the sub-list for method input_type
38, // [38:38] is the sub-list for extension type_name
38, // [38:38] is the sub-list for extension extendee
0, // [0:38] is the sub-list for field type_name
}
func init() { file_provisionerd_proto_provisionerd_proto_init() }
+4
View File
@@ -74,6 +74,7 @@ message CompletedJob {
bytes state = 1;
repeated provisioner.Resource resources = 2;
repeated provisioner.Timing timings = 3;
repeated provisioner.Module modules = 4;
}
message TemplateImport {
repeated provisioner.Resource start_resources = 1;
@@ -81,9 +82,12 @@ message CompletedJob {
repeated provisioner.RichParameter rich_parameters = 3;
repeated string external_auth_providers_names = 4;
repeated provisioner.ExternalAuthProviderResource external_auth_providers = 5;
repeated provisioner.Module start_modules = 6;
repeated provisioner.Module stop_modules = 7;
}
message TemplateDryRun {
repeated provisioner.Resource resources = 1;
repeated provisioner.Module modules = 2;
}
string job_id = 1;
+9
View File
@@ -581,6 +581,8 @@ func (r *Runner) runTemplateImport(ctx context.Context) (*proto.CompletedJob, *p
RichParameters: startProvision.Parameters,
ExternalAuthProvidersNames: externalAuthProviderNames,
ExternalAuthProviders: startProvision.ExternalAuthProviders,
StartModules: startProvision.Modules,
StopModules: stopProvision.Modules,
},
},
}, nil
@@ -640,6 +642,7 @@ type templateImportProvision struct {
Resources []*sdkproto.Resource
Parameters []*sdkproto.RichParameter
ExternalAuthProviders []*sdkproto.ExternalAuthProviderResource
Modules []*sdkproto.Module
}
// Performs a dry-run provision when importing a template.
@@ -731,6 +734,7 @@ func (r *Runner) runTemplateImportProvisionWithRichParameters(
Resources: c.Resources,
Parameters: c.Parameters,
ExternalAuthProviders: c.ExternalAuthProviders,
Modules: c.Modules,
}, nil
default:
return nil, xerrors.Errorf("invalid message type %q received from provisioner",
@@ -793,6 +797,7 @@ func (r *Runner) runTemplateDryRun(ctx context.Context) (*proto.CompletedJob, *p
Type: &proto.CompletedJob_TemplateDryRun_{
TemplateDryRun: &proto.CompletedJob_TemplateDryRun{
Resources: provision.Resources,
Modules: provision.Modules,
},
},
}, nil
@@ -1033,6 +1038,10 @@ func (r *Runner) runWorkspaceBuild(ctx context.Context) (*proto.CompletedJob, *p
State: applyComplete.State,
Resources: applyComplete.Resources,
Timings: applyComplete.Timings,
// Modules are created on disk by `terraform init`, and that is only
// called by `plan`. `apply` does not modify them, so we can use the
// modules from the plan response.
Modules: planComplete.Modules,
},
},
}, nil
+432 -329
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -204,6 +204,13 @@ message Resource {
string icon = 6;
string instance_type = 7;
int32 daily_cost = 8;
string module_path = 9;
}
message Module {
string source = 1;
string version = 2;
string key = 3;
}
// WorkspaceTransition is the desired outcome of a build
@@ -271,6 +278,7 @@ message PlanComplete {
repeated RichParameter parameters = 3;
repeated ExternalAuthProviderResource external_auth_providers = 4;
repeated Timing timings = 6;
repeated Module modules = 7;
}
// ApplyRequest asks the provisioner to apply the changes. Apply MUST be preceded by a successful plan request/response
+2
View File
@@ -608,6 +608,7 @@ const createTemplateVersionTar = async (
metadata: [],
name: "dev",
type: "echo",
modulePath: "",
...resource,
} as Resource;
};
@@ -636,6 +637,7 @@ const createTemplateVersionTar = async (
parameters: [],
externalAuthProviders: [],
timings: [],
modules: [],
...response.plan,
} as PlanComplete;
response.plan.resources = response.plan.resources?.map(fillResource);
+32
View File
@@ -215,6 +215,7 @@ export interface Resource {
icon: string;
instanceType: string;
dailyCost: number;
modulePath: string;
}
export interface Resource_Metadata {
@@ -224,6 +225,12 @@ export interface Resource_Metadata {
isNull: boolean;
}
export interface Module {
source: string;
version: string;
key: string;
}
/** Metadata is information about a workspace used in the execution of a build */
export interface Metadata {
coderUrl: string;
@@ -286,6 +293,7 @@ export interface PlanComplete {
parameters: RichParameter[];
externalAuthProviders: ExternalAuthProviderResource[];
timings: Timing[];
modules: Module[];
}
/**
@@ -798,6 +806,9 @@ export const Resource = {
if (message.dailyCost !== 0) {
writer.uint32(64).int32(message.dailyCost);
}
if (message.modulePath !== "") {
writer.uint32(74).string(message.modulePath);
}
return writer;
},
};
@@ -823,6 +834,24 @@ export const Resource_Metadata = {
},
};
export const Module = {
encode(
message: Module,
writer: _m0.Writer = _m0.Writer.create(),
): _m0.Writer {
if (message.source !== "") {
writer.uint32(10).string(message.source);
}
if (message.version !== "") {
writer.uint32(18).string(message.version);
}
if (message.key !== "") {
writer.uint32(26).string(message.key);
}
return writer;
},
};
export const Metadata = {
encode(
message: Metadata,
@@ -996,6 +1025,9 @@ export const PlanComplete = {
for (const v of message.timings) {
Timing.encode(v!, writer.uint32(50).fork()).ldelim();
}
for (const v of message.modules) {
Module.encode(v!, writer.uint32(58).fork()).ldelim();
}
return writer;
},
};