Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e018d37e3 | |||
| f14b590254 | |||
| 83e0e0b5df | |||
| 5e09b91cbc | |||
| ad1dddb309 |
@@ -402,6 +402,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{takeFirst(orig.SubagentID, uuid.NullUUID{}).UUID},
|
||||
})
|
||||
require.NoError(t, err, "insert workspace agent devcontainer")
|
||||
return devcontainers[0]
|
||||
|
||||
Generated
+2
-1
@@ -2505,7 +2505,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';
|
||||
|
||||
@@ -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;
|
||||
@@ -4743,7 +4743,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 {
|
||||
|
||||
@@ -7989,3 +7989,99 @@ func TestDeleteExpiredAPIKeys(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, remaining, len(unexpiredTimes))
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentDevcontainersSubagentID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
// Setup: create workspace agent
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
tpl := dbgen.Template(t, db, database.Template{
|
||||
OrganizationID: org.ID,
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
tv := dbgen.TemplateVersion(t, db, database.TemplateVersion{
|
||||
OrganizationID: org.ID,
|
||||
TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true},
|
||||
CreatedBy: user.ID,
|
||||
})
|
||||
ws := dbgen.Workspace(t, db, database.WorkspaceTable{
|
||||
OrganizationID: org.ID,
|
||||
OwnerID: user.ID,
|
||||
TemplateID: tpl.ID,
|
||||
})
|
||||
job := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
|
||||
OrganizationID: org.ID,
|
||||
})
|
||||
build := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||
WorkspaceID: ws.ID,
|
||||
JobID: job.ID,
|
||||
TemplateVersionID: tv.ID,
|
||||
})
|
||||
res := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{
|
||||
JobID: build.JobID,
|
||||
})
|
||||
agent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
|
||||
ResourceID: res.ID,
|
||||
})
|
||||
|
||||
// Create a subagent that will be referenced
|
||||
subagent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
|
||||
ResourceID: res.ID,
|
||||
ParentID: uuid.NullUUID{UUID: agent.ID, Valid: true},
|
||||
})
|
||||
|
||||
t.Run("InsertWithSubagentID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
devcontainers, err := db.InsertWorkspaceAgentDevcontainers(ctx, database.InsertWorkspaceAgentDevcontainersParams{
|
||||
WorkspaceAgentID: agent.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
ID: []uuid.UUID{uuid.New()},
|
||||
Name: []string{"test-devcontainer"},
|
||||
WorkspaceFolder: []string{"/workspace"},
|
||||
ConfigPath: []string{"/workspace/.devcontainer/devcontainer.json"},
|
||||
SubagentID: []uuid.UUID{subagent.ID},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, devcontainers, 1)
|
||||
require.True(t, devcontainers[0].SubagentID.Valid)
|
||||
require.Equal(t, subagent.ID, devcontainers[0].SubagentID.UUID)
|
||||
|
||||
// Verify retrieval
|
||||
retrieved, err := db.GetWorkspaceAgentDevcontainersByAgentID(ctx, agent.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, retrieved, 1)
|
||||
require.True(t, retrieved[0].SubagentID.Valid)
|
||||
require.Equal(t, subagent.ID, retrieved[0].SubagentID.UUID)
|
||||
})
|
||||
|
||||
t.Run("InsertWithNilSubagentID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
// Create a separate agent for this subtest
|
||||
agent2 := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
|
||||
ResourceID: res.ID,
|
||||
})
|
||||
|
||||
// When uuid.Nil is passed, it stores the zero UUID (not NULL).
|
||||
// This matches the provisionerdserver behavior.
|
||||
devcontainers, err := db.InsertWorkspaceAgentDevcontainers(ctx, database.InsertWorkspaceAgentDevcontainersParams{
|
||||
WorkspaceAgentID: agent2.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
ID: []uuid.UUID{uuid.New()},
|
||||
Name: []string{"no-subagent"},
|
||||
WorkspaceFolder: []string{"/workspace"},
|
||||
ConfigPath: []string{""},
|
||||
SubagentID: []uuid.UUID{uuid.Nil},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, devcontainers, 1)
|
||||
// uuid.Nil is stored as a zero UUID, not NULL.
|
||||
require.Equal(t, uuid.Nil, devcontainers[0].SubagentID.UUID)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17336,7 +17336,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
|
||||
@@ -17361,6 +17361,7 @@ func (q *sqlQuerier) GetWorkspaceAgentDevcontainersByAgentID(ctx context.Context
|
||||
&i.WorkspaceFolder,
|
||||
&i.ConfigPath,
|
||||
&i.Name,
|
||||
&i.SubagentID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -17377,15 +17378,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,
|
||||
unnest($7::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 {
|
||||
@@ -17395,6 +17397,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) {
|
||||
@@ -17405,6 +17408,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
|
||||
@@ -17420,6 +17424,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,
|
||||
unnest(@subagent_id::uuid[]) AS subagent_id
|
||||
RETURNING workspace_agent_devcontainers.*;
|
||||
|
||||
-- name: GetWorkspaceAgentDevcontainersByAgentID :many
|
||||
|
||||
@@ -2897,6 +2897,7 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
||||
devcontainerNames = make([]string, 0, len(devcontainers))
|
||||
devcontainerWorkspaceFolders = make([]string, 0, len(devcontainers))
|
||||
devcontainerConfigPaths = make([]string, 0, len(devcontainers))
|
||||
devcontainerSubagentIDs = make([]uuid.UUID, 0, len(devcontainers))
|
||||
)
|
||||
for _, dc := range devcontainers {
|
||||
id := uuid.New()
|
||||
@@ -2905,6 +2906,22 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
||||
devcontainerWorkspaceFolders = append(devcontainerWorkspaceFolders, dc.WorkspaceFolder)
|
||||
devcontainerConfigPaths = append(devcontainerConfigPaths, dc.ConfigPath)
|
||||
|
||||
var subagentID uuid.UUID
|
||||
hasSubagentID := len(dc.SubagentId) > 0
|
||||
if hasSubagentID {
|
||||
subagentID, err = uuid.FromBytes(dc.SubagentId)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse devcontainer %q subagent_id: %w", dc.Name, err)
|
||||
}
|
||||
}
|
||||
if hasSubagentID && (len(dc.Apps) > 0 || len(dc.Scripts) > 0 || len(dc.Envs) > 0) {
|
||||
subagentID, err = insertDevcontainerSubagent(ctx, db, subagentID, dc, prAgent, agentID, resource.ID, snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
devcontainerSubagentIDs = append(devcontainerSubagentIDs, subagentID)
|
||||
|
||||
// Add a log source and script for each devcontainer so we can
|
||||
// track logs and timings for each devcontainer.
|
||||
displayName := fmt.Sprintf("Dev Container (%s)", dc.Name)
|
||||
@@ -2932,6 +2949,7 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
||||
Name: devcontainerNames,
|
||||
WorkspaceFolder: devcontainerWorkspaceFolders,
|
||||
ConfigPath: devcontainerConfigPaths,
|
||||
SubagentID: devcontainerSubagentIDs,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert agent devcontainer: %w", err)
|
||||
@@ -2983,35 +3001,18 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
||||
}
|
||||
appSlugs[slug] = struct{}{}
|
||||
|
||||
health := database.WorkspaceAppHealthDisabled
|
||||
if app.Healthcheck == nil {
|
||||
app.Healthcheck = &sdkproto.Healthcheck{}
|
||||
}
|
||||
if app.Healthcheck.Url != "" {
|
||||
health = database.WorkspaceAppHealthInitializing
|
||||
}
|
||||
|
||||
sharingLevel := database.AppSharingLevelOwner
|
||||
switch app.SharingLevel {
|
||||
case sdkproto.AppSharingLevel_AUTHENTICATED:
|
||||
sharingLevel = database.AppSharingLevelAuthenticated
|
||||
case sdkproto.AppSharingLevel_PUBLIC:
|
||||
sharingLevel = database.AppSharingLevelPublic
|
||||
}
|
||||
health := appHealthFromHealthcheck(app.Healthcheck)
|
||||
sharingLevel := appSharingLevelToDatabase(app.SharingLevel)
|
||||
openIn := appOpenInToDatabase(app.OpenIn)
|
||||
|
||||
displayGroup := sql.NullString{
|
||||
Valid: app.Group != "",
|
||||
String: app.Group,
|
||||
}
|
||||
|
||||
openIn := database.WorkspaceAppOpenInSlimWindow
|
||||
switch app.OpenIn {
|
||||
case sdkproto.AppOpenIn_TAB:
|
||||
openIn = database.WorkspaceAppOpenInTab
|
||||
case sdkproto.AppOpenIn_SLIM_WINDOW:
|
||||
openIn = database.WorkspaceAppOpenInSlimWindow
|
||||
}
|
||||
|
||||
var appID string
|
||||
if app.Id == "" || app.Id == uuid.Nil.String() {
|
||||
appID = uuid.NewString()
|
||||
@@ -3362,3 +3363,251 @@ func convertDisplayApps(apps *sdkproto.DisplayApps) []database.DisplayApp {
|
||||
}
|
||||
return dapps
|
||||
}
|
||||
|
||||
// appSharingLevelToDatabase converts a proto app sharing level to a database
|
||||
// app sharing level.
|
||||
func appSharingLevelToDatabase(level sdkproto.AppSharingLevel) database.AppSharingLevel {
|
||||
switch level {
|
||||
case sdkproto.AppSharingLevel_AUTHENTICATED:
|
||||
return database.AppSharingLevelAuthenticated
|
||||
case sdkproto.AppSharingLevel_PUBLIC:
|
||||
return database.AppSharingLevelPublic
|
||||
default:
|
||||
return database.AppSharingLevelOwner
|
||||
}
|
||||
}
|
||||
|
||||
// appOpenInToDatabase converts a proto app open_in setting to a database
|
||||
// workspace app open_in setting.
|
||||
func appOpenInToDatabase(openIn sdkproto.AppOpenIn) database.WorkspaceAppOpenIn {
|
||||
switch openIn {
|
||||
case sdkproto.AppOpenIn_TAB:
|
||||
return database.WorkspaceAppOpenInTab
|
||||
default:
|
||||
return database.WorkspaceAppOpenInSlimWindow
|
||||
}
|
||||
}
|
||||
|
||||
// appHealthFromHealthcheck returns the initial health status for an app based
|
||||
// on whether it has a healthcheck URL configured.
|
||||
func appHealthFromHealthcheck(hc *sdkproto.Healthcheck) database.WorkspaceAppHealth {
|
||||
if hc != nil && hc.Url != "" {
|
||||
return database.WorkspaceAppHealthInitializing
|
||||
}
|
||||
return database.WorkspaceAppHealthDisabled
|
||||
}
|
||||
|
||||
// insertDevcontainerSubagent creates a subagent for a devcontainer with its apps, scripts, and envs.
|
||||
// If subagentID is uuid.Nil, a new UUID will be generated.
|
||||
func insertDevcontainerSubagent(
|
||||
ctx context.Context,
|
||||
db database.Store,
|
||||
subagentID uuid.UUID,
|
||||
dc *sdkproto.Devcontainer,
|
||||
parentAgent *sdkproto.Agent,
|
||||
parentAgentID uuid.UUID,
|
||||
resourceID uuid.UUID,
|
||||
snapshot *telemetry.Snapshot,
|
||||
) (uuid.UUID, error) {
|
||||
if subagentID == uuid.Nil {
|
||||
subagentID = uuid.New()
|
||||
}
|
||||
|
||||
subAgentEnvs := make(map[string]string, len(dc.Envs))
|
||||
for _, env := range dc.Envs {
|
||||
subAgentEnvs[env.Name] = env.Value
|
||||
}
|
||||
var subAgentEnvsJSON pqtype.NullRawMessage
|
||||
if len(subAgentEnvs) > 0 {
|
||||
envJSON, err := json.Marshal(subAgentEnvs)
|
||||
if err != nil {
|
||||
return uuid.Nil, xerrors.Errorf("marshal devcontainer %q envs: %w", dc.Name, err)
|
||||
}
|
||||
subAgentEnvsJSON = pqtype.NullRawMessage{RawMessage: envJSON, Valid: true}
|
||||
}
|
||||
|
||||
_, err := db.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{
|
||||
ID: subagentID,
|
||||
ParentID: uuid.NullUUID{Valid: true, UUID: parentAgentID},
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
ResourceID: resourceID,
|
||||
Name: dc.Name,
|
||||
AuthToken: uuid.New(),
|
||||
AuthInstanceID: sql.NullString{},
|
||||
Architecture: parentAgent.Architecture,
|
||||
EnvironmentVariables: subAgentEnvsJSON,
|
||||
Directory: dc.WorkspaceFolder,
|
||||
OperatingSystem: parentAgent.OperatingSystem,
|
||||
ConnectionTimeoutSeconds: parentAgent.GetConnectionTimeoutSeconds(),
|
||||
TroubleshootingURL: parentAgent.GetTroubleshootingUrl(),
|
||||
MOTDFile: "",
|
||||
DisplayApps: []database.DisplayApp{},
|
||||
InstanceMetadata: pqtype.NullRawMessage{},
|
||||
ResourceMetadata: pqtype.NullRawMessage{},
|
||||
DisplayOrder: 0,
|
||||
APIKeyScope: database.AgentKeyScopeEnumAll,
|
||||
})
|
||||
if err != nil {
|
||||
return uuid.Nil, xerrors.Errorf("insert devcontainer %q subagent: %w", dc.Name, err)
|
||||
}
|
||||
|
||||
if err := insertDevcontainerSubagentApps(ctx, db, dc, subagentID, snapshot); err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
|
||||
if err := insertDevcontainerSubagentScripts(ctx, db, dc, subagentID); err != nil {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
|
||||
return subagentID, nil
|
||||
}
|
||||
|
||||
// insertDevcontainerSubagentApps inserts workspace apps for a devcontainer subagent.
|
||||
func insertDevcontainerSubagentApps(
|
||||
ctx context.Context,
|
||||
db database.Store,
|
||||
dc *sdkproto.Devcontainer,
|
||||
subagentID uuid.UUID,
|
||||
snapshot *telemetry.Snapshot,
|
||||
) error {
|
||||
for _, app := range dc.Apps {
|
||||
slug := app.Slug
|
||||
if slug == "" {
|
||||
return xerrors.Errorf("devcontainer %q app must have a slug set", dc.Name)
|
||||
}
|
||||
if !provisioner.AppSlugRegex.MatchString(slug) {
|
||||
return xerrors.Errorf("devcontainer %q app slug %q does not match regex %q", dc.Name, slug, provisioner.AppSlugRegex.String())
|
||||
}
|
||||
|
||||
if app.Healthcheck == nil {
|
||||
app.Healthcheck = &sdkproto.Healthcheck{}
|
||||
}
|
||||
health := appHealthFromHealthcheck(app.Healthcheck)
|
||||
sharingLevel := appSharingLevelToDatabase(app.SharingLevel)
|
||||
openIn := appOpenInToDatabase(app.OpenIn)
|
||||
|
||||
displayGroup := sql.NullString{
|
||||
Valid: app.Group != "",
|
||||
String: app.Group,
|
||||
}
|
||||
|
||||
appID := uuid.New()
|
||||
if app.Id != "" && app.Id != uuid.Nil.String() {
|
||||
var err error
|
||||
appID, err = uuid.Parse(app.Id)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse devcontainer %q app uuid: %w", dc.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
dbApp, err := db.UpsertWorkspaceApp(ctx, database.UpsertWorkspaceAppParams{
|
||||
ID: appID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
AgentID: subagentID,
|
||||
Slug: slug,
|
||||
DisplayName: app.DisplayName,
|
||||
Icon: app.Icon,
|
||||
Command: sql.NullString{
|
||||
String: app.Command,
|
||||
Valid: app.Command != "",
|
||||
},
|
||||
Url: sql.NullString{
|
||||
String: app.Url,
|
||||
Valid: app.Url != "",
|
||||
},
|
||||
External: app.External,
|
||||
Subdomain: app.Subdomain,
|
||||
SharingLevel: sharingLevel,
|
||||
HealthcheckUrl: app.Healthcheck.Url,
|
||||
HealthcheckInterval: app.Healthcheck.Interval,
|
||||
HealthcheckThreshold: app.Healthcheck.Threshold,
|
||||
Health: health,
|
||||
// #nosec G115 - Order represents a display order value that's always small and fits in int32
|
||||
DisplayOrder: int32(app.Order),
|
||||
DisplayGroup: displayGroup,
|
||||
Hidden: app.Hidden,
|
||||
OpenIn: openIn,
|
||||
Tooltip: app.Tooltip,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("upsert devcontainer %q app: %w", dc.Name, err)
|
||||
}
|
||||
snapshot.WorkspaceApps = append(snapshot.WorkspaceApps, telemetry.ConvertWorkspaceApp(dbApp))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// insertDevcontainerSubagentScripts inserts scripts and log sources for a devcontainer subagent.
|
||||
func insertDevcontainerSubagentScripts(
|
||||
ctx context.Context,
|
||||
db database.Store,
|
||||
dc *sdkproto.Devcontainer,
|
||||
subagentID uuid.UUID,
|
||||
) error {
|
||||
if len(dc.Scripts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
logSourceIDs = make([]uuid.UUID, 0, len(dc.Scripts))
|
||||
logSourceNames = make([]string, 0, len(dc.Scripts))
|
||||
logSourceIcons = make([]string, 0, len(dc.Scripts))
|
||||
scriptIDs = make([]uuid.UUID, 0, len(dc.Scripts))
|
||||
scriptLogPaths = make([]string, 0, len(dc.Scripts))
|
||||
scriptSources = make([]string, 0, len(dc.Scripts))
|
||||
scriptCron = make([]string, 0, len(dc.Scripts))
|
||||
scriptTimeout = make([]int32, 0, len(dc.Scripts))
|
||||
scriptStartBlock = make([]bool, 0, len(dc.Scripts))
|
||||
scriptRunOnStart = make([]bool, 0, len(dc.Scripts))
|
||||
scriptRunOnStop = make([]bool, 0, len(dc.Scripts))
|
||||
scriptDisplayNames = make([]string, 0, len(dc.Scripts))
|
||||
)
|
||||
|
||||
for _, script := range dc.Scripts {
|
||||
logSourceID := uuid.New()
|
||||
logSourceIDs = append(logSourceIDs, logSourceID)
|
||||
logSourceNames = append(logSourceNames, script.DisplayName)
|
||||
logSourceIcons = append(logSourceIcons, script.Icon)
|
||||
scriptIDs = append(scriptIDs, uuid.New())
|
||||
scriptLogPaths = append(scriptLogPaths, script.LogPath)
|
||||
scriptSources = append(scriptSources, script.Script)
|
||||
scriptCron = append(scriptCron, script.Cron)
|
||||
scriptTimeout = append(scriptTimeout, script.TimeoutSeconds)
|
||||
scriptStartBlock = append(scriptStartBlock, script.StartBlocksLogin)
|
||||
scriptRunOnStart = append(scriptRunOnStart, script.RunOnStart)
|
||||
scriptRunOnStop = append(scriptRunOnStop, script.RunOnStop)
|
||||
scriptDisplayNames = append(scriptDisplayNames, script.DisplayName)
|
||||
}
|
||||
|
||||
_, err := db.InsertWorkspaceAgentLogSources(ctx, database.InsertWorkspaceAgentLogSourcesParams{
|
||||
WorkspaceAgentID: subagentID,
|
||||
ID: logSourceIDs,
|
||||
CreatedAt: dbtime.Now(),
|
||||
DisplayName: logSourceNames,
|
||||
Icon: logSourceIcons,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert devcontainer %q subagent log sources: %w", dc.Name, err)
|
||||
}
|
||||
|
||||
_, err = db.InsertWorkspaceAgentScripts(ctx, database.InsertWorkspaceAgentScriptsParams{
|
||||
WorkspaceAgentID: subagentID,
|
||||
LogSourceID: logSourceIDs,
|
||||
LogPath: scriptLogPaths,
|
||||
CreatedAt: dbtime.Now(),
|
||||
Script: scriptSources,
|
||||
Cron: scriptCron,
|
||||
TimeoutSeconds: scriptTimeout,
|
||||
StartBlocksLogin: scriptStartBlock,
|
||||
RunOnStart: scriptRunOnStart,
|
||||
RunOnStop: scriptRunOnStop,
|
||||
DisplayName: scriptDisplayNames,
|
||||
ID: scriptIDs,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert devcontainer %q subagent scripts: %w", dc.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3706,6 +3706,7 @@ func TestInsertWorkspaceResource(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
job := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{})
|
||||
subagentID := uuid.New()
|
||||
err := insert(db, job.ID, &sdkproto.Resource{
|
||||
Name: "something",
|
||||
Type: "aws_instance",
|
||||
@@ -3714,6 +3715,7 @@ func TestInsertWorkspaceResource(t *testing.T) {
|
||||
Devcontainers: []*sdkproto.Devcontainer{
|
||||
{Name: "foo", WorkspaceFolder: "/workspace1"},
|
||||
{Name: "bar", WorkspaceFolder: "/workspace2", ConfigPath: "/workspace2/.devcontainer/devcontainer.json"},
|
||||
{Name: "baz", WorkspaceFolder: "/workspace3", SubagentId: subagentID[:]},
|
||||
},
|
||||
}},
|
||||
})
|
||||
@@ -3723,20 +3725,33 @@ func TestInsertWorkspaceResource(t *testing.T) {
|
||||
require.Len(t, resources, 1)
|
||||
agents, err := db.GetWorkspaceAgentsByResourceIDs(ctx, []uuid.UUID{resources[0].ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, agents, 1)
|
||||
agent := agents[0]
|
||||
// Expect 2 agents: the parent agent "dev" and the subagent "baz".
|
||||
require.Len(t, agents, 2)
|
||||
// Find the parent agent (no parent ID).
|
||||
var agent database.WorkspaceAgent
|
||||
for _, a := range agents {
|
||||
if !a.ParentID.Valid {
|
||||
agent = a
|
||||
break
|
||||
}
|
||||
}
|
||||
require.Equal(t, "dev", agent.Name)
|
||||
devcontainers, err := db.GetWorkspaceAgentDevcontainersByAgentID(ctx, agent.ID)
|
||||
sort.Slice(devcontainers, func(i, j int) bool {
|
||||
return devcontainers[i].Name > devcontainers[j].Name
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, devcontainers, 2)
|
||||
require.Len(t, devcontainers, 3)
|
||||
require.Equal(t, "foo", devcontainers[0].Name)
|
||||
require.Equal(t, "/workspace1", devcontainers[0].WorkspaceFolder)
|
||||
require.Equal(t, "", devcontainers[0].ConfigPath)
|
||||
require.Equal(t, "bar", devcontainers[1].Name)
|
||||
require.Equal(t, "/workspace2", devcontainers[1].WorkspaceFolder)
|
||||
require.Equal(t, "/workspace2/.devcontainer/devcontainer.json", devcontainers[1].ConfigPath)
|
||||
require.Equal(t, uuid.Nil, devcontainers[0].SubagentID.UUID)
|
||||
require.Equal(t, "baz", devcontainers[1].Name)
|
||||
require.Equal(t, "/workspace3", devcontainers[1].WorkspaceFolder)
|
||||
require.Equal(t, subagentID, devcontainers[1].SubagentID.UUID)
|
||||
require.Equal(t, "bar", devcontainers[2].Name)
|
||||
require.Equal(t, "/workspace2", devcontainers[2].WorkspaceFolder)
|
||||
require.Equal(t, "/workspace2/.devcontainer/devcontainer.json", devcontainers[2].ConfigPath)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Generated
+593
-536
File diff suppressed because it is too large
Load Diff
@@ -244,6 +244,11 @@ message Devcontainer {
|
||||
string workspace_folder = 1;
|
||||
string config_path = 2;
|
||||
string name = 3;
|
||||
optional bytes id = 4;
|
||||
optional bytes subagent_id = 5;
|
||||
repeated App apps = 6;
|
||||
repeated Script scripts = 7;
|
||||
repeated Env envs = 8;
|
||||
}
|
||||
|
||||
enum AppOpenIn {
|
||||
|
||||
Generated
+20
@@ -306,6 +306,11 @@ export interface Devcontainer {
|
||||
workspaceFolder: string;
|
||||
configPath: string;
|
||||
name: string;
|
||||
id?: Uint8Array | undefined;
|
||||
subagentId?: Uint8Array | undefined;
|
||||
apps: App[];
|
||||
scripts: Script[];
|
||||
envs: Env[];
|
||||
}
|
||||
|
||||
/** App represents a dev-accessible application on the workspace. */
|
||||
@@ -1095,6 +1100,21 @@ export const Devcontainer = {
|
||||
if (message.name !== "") {
|
||||
writer.uint32(26).string(message.name);
|
||||
}
|
||||
if (message.id !== undefined) {
|
||||
writer.uint32(34).bytes(message.id);
|
||||
}
|
||||
if (message.subagentId !== undefined) {
|
||||
writer.uint32(42).bytes(message.subagentId);
|
||||
}
|
||||
for (const v of message.apps) {
|
||||
App.encode(v!, writer.uint32(50).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.scripts) {
|
||||
Script.encode(v!, writer.uint32(58).fork()).ldelim();
|
||||
}
|
||||
for (const v of message.envs) {
|
||||
Env.encode(v!, writer.uint32(66).fork()).ldelim();
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user