mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd/provisionerdserver): insert sub agent resource (#21699)
Update provisionerdserver to handle the changes introduced to provisionerd in https://github.com/coder/coder/pull/21602 We now create a relationship between `workspace_agent_devcontainers` and `workspace_agents` with the newly created `subagent_id`.
This commit is contained in:
@@ -394,6 +394,7 @@ func WorkspaceAgentDevcontainer(t testing.TB, db database.Store, orig database.W
|
||||
Name: []string{takeFirst(orig.Name, testutil.GetRandomName(t))},
|
||||
WorkspaceFolder: []string{takeFirst(orig.WorkspaceFolder, "/workspace")},
|
||||
ConfigPath: []string{takeFirst(orig.ConfigPath, "")},
|
||||
SubagentID: []uuid.UUID{orig.SubagentID.UUID},
|
||||
})
|
||||
require.NoError(t, err, "insert workspace agent devcontainer")
|
||||
return devcontainers[0]
|
||||
|
||||
Generated
+5
-1
@@ -2457,7 +2457,8 @@ CREATE TABLE workspace_agent_devcontainers (
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
workspace_folder text NOT NULL,
|
||||
config_path text NOT NULL,
|
||||
name text NOT NULL
|
||||
name text NOT NULL,
|
||||
subagent_id uuid
|
||||
);
|
||||
|
||||
COMMENT ON TABLE workspace_agent_devcontainers IS 'Workspace agent devcontainer configuration';
|
||||
@@ -3737,6 +3738,9 @@ ALTER TABLE ONLY user_status_changes
|
||||
ALTER TABLE ONLY webpush_subscriptions
|
||||
ADD CONSTRAINT webpush_subscriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY workspace_agent_devcontainers
|
||||
ADD CONSTRAINT workspace_agent_devcontainers_subagent_id_fkey FOREIGN KEY (subagent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY workspace_agent_devcontainers
|
||||
ADD CONSTRAINT workspace_agent_devcontainers_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ const (
|
||||
ForeignKeyUserSecretsUserID ForeignKeyConstraint = "user_secrets_user_id_fkey" // ALTER TABLE ONLY user_secrets ADD CONSTRAINT user_secrets_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyUserStatusChangesUserID ForeignKeyConstraint = "user_status_changes_user_id_fkey" // ALTER TABLE ONLY user_status_changes ADD CONSTRAINT user_status_changes_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
ForeignKeyWebpushSubscriptionsUserID ForeignKeyConstraint = "webpush_subscriptions_user_id_fkey" // ALTER TABLE ONLY webpush_subscriptions ADD CONSTRAINT webpush_subscriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceAgentDevcontainersSubagentID ForeignKeyConstraint = "workspace_agent_devcontainers_subagent_id_fkey" // ALTER TABLE ONLY workspace_agent_devcontainers ADD CONSTRAINT workspace_agent_devcontainers_subagent_id_fkey FOREIGN KEY (subagent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceAgentDevcontainersWorkspaceAgentID ForeignKeyConstraint = "workspace_agent_devcontainers_workspace_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_devcontainers ADD CONSTRAINT workspace_agent_devcontainers_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceAgentLogSourcesWorkspaceAgentID ForeignKeyConstraint = "workspace_agent_log_sources_workspace_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_log_sources ADD CONSTRAINT workspace_agent_log_sources_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
ForeignKeyWorkspaceAgentMemoryResourceMonitorsAgentID ForeignKeyConstraint = "workspace_agent_memory_resource_monitors_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_memory_resource_monitors ADD CONSTRAINT workspace_agent_memory_resource_monitors_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE workspace_agent_devcontainers
|
||||
DROP COLUMN subagent_id;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE workspace_agent_devcontainers
|
||||
ADD COLUMN subagent_id UUID REFERENCES workspace_agents(id) ON DELETE CASCADE;
|
||||
@@ -4771,7 +4771,8 @@ type WorkspaceAgentDevcontainer struct {
|
||||
// Path to devcontainer.json.
|
||||
ConfigPath string `db:"config_path" json:"config_path"`
|
||||
// The name of the Dev Container.
|
||||
Name string `db:"name" json:"name"`
|
||||
Name string `db:"name" json:"name"`
|
||||
SubagentID uuid.NullUUID `db:"subagent_id" json:"subagent_id"`
|
||||
}
|
||||
|
||||
type WorkspaceAgentLog struct {
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -8482,3 +8484,103 @@ func TestGetAuthenticatedWorkspaceAgentAndBuildByAuthToken_ShutdownScripts(t *te
|
||||
require.ErrorIs(t, err, sql.ErrNoRows, "agent should not authenticate when latest build is not STOP")
|
||||
})
|
||||
}
|
||||
|
||||
// Our `InsertWorkspaceAgentDevcontainers` query should ideally be `[]uuid.NullUUID` but unfortunately
|
||||
// sqlc infers it as `[]uuid.UUID`. To ensure we don't insert a `uuid.Nil`, the query inserts NULL when
|
||||
// passed with `uuid.Nil`. This test ensures we keep this behavior without regression.
|
||||
func TestInsertWorkspaceAgentDevcontainers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
validSubagent []bool
|
||||
}{
|
||||
{"BothValid", []bool{true, true}},
|
||||
{"FirstValidSecondInvalid", []bool{true, false}},
|
||||
{"FirstInvalidSecondValid", []bool{false, true}},
|
||||
{"BothInvalid", []bool{false, false}},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
db, _ = dbtestutil.NewDB(t)
|
||||
org = dbgen.Organization(t, db, database.Organization{})
|
||||
job = dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
Type: database.ProvisionerJobTypeTemplateVersionImport,
|
||||
OrganizationID: org.ID,
|
||||
})
|
||||
resource = dbgen.WorkspaceResource(t, db, database.WorkspaceResource{JobID: job.ID})
|
||||
agent = dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ResourceID: resource.ID})
|
||||
)
|
||||
|
||||
ids := make([]uuid.UUID, len(tc.validSubagent))
|
||||
names := make([]string, len(tc.validSubagent))
|
||||
workspaceFolders := make([]string, len(tc.validSubagent))
|
||||
configPaths := make([]string, len(tc.validSubagent))
|
||||
subagentIDs := make([]uuid.UUID, len(tc.validSubagent))
|
||||
|
||||
for i, valid := range tc.validSubagent {
|
||||
ids[i] = uuid.New()
|
||||
names[i] = fmt.Sprintf("test-devcontainer-%d", i)
|
||||
workspaceFolders[i] = fmt.Sprintf("/workspace%d", i)
|
||||
configPaths[i] = fmt.Sprintf("/workspace%d/.devcontainer/devcontainer.json", i)
|
||||
|
||||
if valid {
|
||||
subagentIDs[i] = dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
|
||||
ResourceID: resource.ID,
|
||||
ParentID: uuid.NullUUID{UUID: agent.ID, Valid: true},
|
||||
}).ID
|
||||
} else {
|
||||
subagentIDs[i] = uuid.Nil
|
||||
}
|
||||
}
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
// Given: We insert multiple devcontainer records.
|
||||
devcontainers, err := db.InsertWorkspaceAgentDevcontainers(ctx, database.InsertWorkspaceAgentDevcontainersParams{
|
||||
WorkspaceAgentID: agent.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
ID: ids,
|
||||
Name: names,
|
||||
WorkspaceFolder: workspaceFolders,
|
||||
ConfigPath: configPaths,
|
||||
SubagentID: subagentIDs,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, devcontainers, len(tc.validSubagent))
|
||||
|
||||
// Then: Verify each devcontainer has the correct SubagentID validity.
|
||||
// - When we pass `uuid.Nil`, we get a `uuid.NullUUID{Valid: false}`
|
||||
// - When we pass a valid UUID, we get a `uuid.NullUUID{Valid: true}`
|
||||
for i, valid := range tc.validSubagent {
|
||||
require.Equal(t, valid, devcontainers[i].SubagentID.Valid, "devcontainer %d: subagent_id validity mismatch", i)
|
||||
if valid {
|
||||
require.Equal(t, subagentIDs[i], devcontainers[i].SubagentID.UUID, "devcontainer %d: subagent_id UUID mismatch", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the same check on data returned by
|
||||
// `GetWorkspaceAgentDevcontainersByAgentID` to ensure the fix is at
|
||||
// the data storage layer, instead of just at a query level.
|
||||
fetched, err := db.GetWorkspaceAgentDevcontainersByAgentID(ctx, agent.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fetched, len(tc.validSubagent))
|
||||
|
||||
// Sort fetched by name to ensure consistent ordering for comparison.
|
||||
slices.SortFunc(fetched, func(a, b database.WorkspaceAgentDevcontainer) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
|
||||
for i, valid := range tc.validSubagent {
|
||||
require.Equal(t, valid, fetched[i].SubagentID.Valid, "fetched devcontainer %d: subagent_id validity mismatch", i)
|
||||
if valid {
|
||||
require.Equal(t, subagentIDs[i], fetched[i].SubagentID.UUID, "fetched devcontainer %d: subagent_id UUID mismatch", i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17213,7 +17213,7 @@ func (q *sqlQuerier) ValidateUserIDs(ctx context.Context, userIds []uuid.UUID) (
|
||||
|
||||
const getWorkspaceAgentDevcontainersByAgentID = `-- name: GetWorkspaceAgentDevcontainersByAgentID :many
|
||||
SELECT
|
||||
id, workspace_agent_id, created_at, workspace_folder, config_path, name
|
||||
id, workspace_agent_id, created_at, workspace_folder, config_path, name, subagent_id
|
||||
FROM
|
||||
workspace_agent_devcontainers
|
||||
WHERE
|
||||
@@ -17238,6 +17238,7 @@ func (q *sqlQuerier) GetWorkspaceAgentDevcontainersByAgentID(ctx context.Context
|
||||
&i.WorkspaceFolder,
|
||||
&i.ConfigPath,
|
||||
&i.Name,
|
||||
&i.SubagentID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -17254,15 +17255,16 @@ func (q *sqlQuerier) GetWorkspaceAgentDevcontainersByAgentID(ctx context.Context
|
||||
|
||||
const insertWorkspaceAgentDevcontainers = `-- name: InsertWorkspaceAgentDevcontainers :many
|
||||
INSERT INTO
|
||||
workspace_agent_devcontainers (workspace_agent_id, created_at, id, name, workspace_folder, config_path)
|
||||
workspace_agent_devcontainers (workspace_agent_id, created_at, id, name, workspace_folder, config_path, subagent_id)
|
||||
SELECT
|
||||
$1::uuid AS workspace_agent_id,
|
||||
$2::timestamptz AS created_at,
|
||||
unnest($3::uuid[]) AS id,
|
||||
unnest($4::text[]) AS name,
|
||||
unnest($5::text[]) AS workspace_folder,
|
||||
unnest($6::text[]) AS config_path
|
||||
RETURNING workspace_agent_devcontainers.id, workspace_agent_devcontainers.workspace_agent_id, workspace_agent_devcontainers.created_at, workspace_agent_devcontainers.workspace_folder, workspace_agent_devcontainers.config_path, workspace_agent_devcontainers.name
|
||||
unnest($6::text[]) AS config_path,
|
||||
NULLIF(unnest($7::uuid[]), '00000000-0000-0000-0000-000000000000')::uuid AS subagent_id
|
||||
RETURNING workspace_agent_devcontainers.id, workspace_agent_devcontainers.workspace_agent_id, workspace_agent_devcontainers.created_at, workspace_agent_devcontainers.workspace_folder, workspace_agent_devcontainers.config_path, workspace_agent_devcontainers.name, workspace_agent_devcontainers.subagent_id
|
||||
`
|
||||
|
||||
type InsertWorkspaceAgentDevcontainersParams struct {
|
||||
@@ -17272,6 +17274,7 @@ type InsertWorkspaceAgentDevcontainersParams struct {
|
||||
Name []string `db:"name" json:"name"`
|
||||
WorkspaceFolder []string `db:"workspace_folder" json:"workspace_folder"`
|
||||
ConfigPath []string `db:"config_path" json:"config_path"`
|
||||
SubagentID []uuid.UUID `db:"subagent_id" json:"subagent_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertWorkspaceAgentDevcontainers(ctx context.Context, arg InsertWorkspaceAgentDevcontainersParams) ([]WorkspaceAgentDevcontainer, error) {
|
||||
@@ -17282,6 +17285,7 @@ func (q *sqlQuerier) InsertWorkspaceAgentDevcontainers(ctx context.Context, arg
|
||||
pq.Array(arg.Name),
|
||||
pq.Array(arg.WorkspaceFolder),
|
||||
pq.Array(arg.ConfigPath),
|
||||
pq.Array(arg.SubagentID),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -17297,6 +17301,7 @@ func (q *sqlQuerier) InsertWorkspaceAgentDevcontainers(ctx context.Context, arg
|
||||
&i.WorkspaceFolder,
|
||||
&i.ConfigPath,
|
||||
&i.Name,
|
||||
&i.SubagentID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
-- name: InsertWorkspaceAgentDevcontainers :many
|
||||
INSERT INTO
|
||||
workspace_agent_devcontainers (workspace_agent_id, created_at, id, name, workspace_folder, config_path)
|
||||
workspace_agent_devcontainers (workspace_agent_id, created_at, id, name, workspace_folder, config_path, subagent_id)
|
||||
SELECT
|
||||
@workspace_agent_id::uuid AS workspace_agent_id,
|
||||
@created_at::timestamptz AS created_at,
|
||||
unnest(@id::uuid[]) AS id,
|
||||
unnest(@name::text[]) AS name,
|
||||
unnest(@workspace_folder::text[]) AS workspace_folder,
|
||||
unnest(@config_path::text[]) AS config_path
|
||||
unnest(@config_path::text[]) AS config_path,
|
||||
NULLIF(unnest(@subagent_id::uuid[]), '00000000-0000-0000-0000-000000000000')::uuid AS subagent_id
|
||||
RETURNING workspace_agent_devcontainers.*;
|
||||
|
||||
-- name: GetWorkspaceAgentDevcontainersByAgentID :many
|
||||
|
||||
Reference in New Issue
Block a user