Skip to content

feat(agent): add devcontainer autostart support #17076

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Mar 27, 2025
Merged
Prev Previous commit
Next Next commit
use name, print warning
  • Loading branch information
mafredri committed Mar 25, 2025
commit e1048b1875cf0beeafad1fefdf2ef9efeb514d84
26 changes: 13 additions & 13 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -1124,21 +1124,21 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context,
// initialized (git clone, etc).
postStartScripts []codersdk.WorkspaceAgentScript
)
for _, dc := range manifest.Devcontainers {
// TODO(mafredri): Verify `@devcontainers/cli` presence.
// TODO(mafredri): Verify workspace folder exists.
// TODO(mafredri): If set, verify config path exists.
dc = expandDevcontainerPaths(a.logger, dc)

for i, s := range scripts {
// The devcontainer scripts match the devcontainer ID for
// identification.
if s.ID == dc.ID {
scripts = slices.Delete(scripts, i, i+1)
if a.experimentalDevcontainersEnabled {
if a.experimentalDevcontainersEnabled {
for _, dc := range manifest.Devcontainers {
// TODO(mafredri): Verify `@devcontainers/cli` presence.
// TODO(mafredri): Verify workspace folder exists.
// TODO(mafredri): If set, verify config path exists.
dc = expandDevcontainerPaths(a.logger, dc)

for i, s := range scripts {
// The devcontainer scripts match the devcontainer ID for
// identification.
if s.ID == dc.ID {
scripts = slices.Delete(scripts, i, i+1)
postStartScripts = append(postStartScripts, agentcontainers.DevcontainerStartupScript(dc, s))
break
}
break
}
}
}
Expand Down
1 change: 1 addition & 0 deletions agent/agentcontainers/devcontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ func DevcontainerStartupScript(dc codersdk.WorkspaceAgentDevcontainer, script co
if dc.ConfigPath != "" {
cmd = fmt.Sprintf("%s --config %q", cmd, dc.ConfigPath)
}
script.RunOnStart = false
script.Script = cmd
return script
}
91 changes: 48 additions & 43 deletions coderd/provisionerdserver/provisionerdserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2051,34 +2051,6 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
}
}

var (
devcontainers = prAgent.GetDevcontainers()
devcontainerIDs = make([]uuid.UUID, 0, len(devcontainers))
devcontainerNames = make([]string, 0, len(devcontainers))
devcontainerWorkspaceFolders = make([]string, 0, len(devcontainers))
devcontainerConfigPaths = make([]string, 0, len(devcontainers))
)
if len(devcontainers) > 0 {
for _, dc := range devcontainers {
devcontainerIDs = append(devcontainerIDs, uuid.New())
devcontainerNames = append(devcontainerNames, dc.Name)
devcontainerWorkspaceFolders = append(devcontainerWorkspaceFolders, dc.WorkspaceFolder)
devcontainerConfigPaths = append(devcontainerConfigPaths, dc.ConfigPath)
}

_, err = db.InsertWorkspaceAgentDevcontainers(ctx, database.InsertWorkspaceAgentDevcontainersParams{
WorkspaceAgentID: agentID,
CreatedAt: dbtime.Now(),
ID: devcontainerIDs,
Name: devcontainerNames,
WorkspaceFolder: devcontainerWorkspaceFolders,
ConfigPath: devcontainerConfigPaths,
})
if err != nil {
return xerrors.Errorf("insert agent devcontainer: %w", err)
}
}

logSourceIDs := make([]uuid.UUID, 0, len(prAgent.Scripts))
logSourceDisplayNames := make([]string, 0, len(prAgent.Scripts))
logSourceIcons := make([]string, 0, len(prAgent.Scripts))
Expand Down Expand Up @@ -2106,21 +2078,54 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
scriptRunOnStart = append(scriptRunOnStart, script.RunOnStart)
scriptRunOnStop = append(scriptRunOnStop, script.RunOnStop)
}
// Add a log source and script for each devcontainer so we can
// track logs and timings for each.
for _, id := range devcontainerIDs {
logSourceIDs = append(logSourceIDs, uuid.New())
logSourceDisplayNames = append(logSourceDisplayNames, "Dev Container")
logSourceIcons = append(logSourceIcons, "/emojis/1f4e6.png") // Emoji package. Or perhaps /icon/container.svg?
scriptIDs = append(scriptIDs, id) // Re-use the devcontainer ID as the script ID for identification.
scriptDisplayName = append(scriptDisplayName, "Dev Container") // TODO(mafredri): Make it unique? Grab from id used in TF?
scriptLogPaths = append(scriptLogPaths, "")
scriptSources = append(scriptSources, "")
scriptCron = append(scriptCron, "")
scriptTimeout = append(scriptTimeout, 0)
scriptStartBlocksLogin = append(scriptStartBlocksLogin, false)
scriptRunOnStart = append(scriptRunOnStart, false)
scriptRunOnStop = append(scriptRunOnStop, false)

// Dev Containers require a script and log/source, so we do this before
// the logs insert below.
if devcontainers := prAgent.GetDevcontainers(); len(devcontainers) > 0 {
var (
devcontainerIDs = make([]uuid.UUID, 0, len(devcontainers))
devcontainerNames = make([]string, 0, len(devcontainers))
devcontainerWorkspaceFolders = make([]string, 0, len(devcontainers))
devcontainerConfigPaths = make([]string, 0, len(devcontainers))
)
for _, dc := range devcontainers {
id := uuid.New()
devcontainerIDs = append(devcontainerIDs, id)
devcontainerNames = append(devcontainerNames, dc.Name)
devcontainerWorkspaceFolders = append(devcontainerWorkspaceFolders, dc.WorkspaceFolder)
devcontainerConfigPaths = append(devcontainerConfigPaths, dc.ConfigPath)

// 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)
logSourceIDs = append(logSourceIDs, uuid.New())
logSourceDisplayNames = append(logSourceDisplayNames, displayName)
logSourceIcons = append(logSourceIcons, "/emojis/1f4e6.png") // Emoji package. Or perhaps /icon/container.svg?
scriptIDs = append(scriptIDs, id) // Re-use the devcontainer ID as the script ID for identification.
scriptDisplayName = append(scriptDisplayName, displayName)
scriptLogPaths = append(scriptLogPaths, "")
scriptSources = append(scriptSources, `echo "WARNING: Dev Containers are early access. If you're seeing this message then Dev Containers haven't been enabled for your workspace yet. To enable, the agent needs to run with the environment variable CODER_AGENT_DEVCONTAINERS_ENABLE=true set."`)
scriptCron = append(scriptCron, "")
scriptTimeout = append(scriptTimeout, 0)
scriptStartBlocksLogin = append(scriptStartBlocksLogin, false)
// Run on start to surface the warning message in case the
// terraform resource is used, but the experiment hasn't
// been enabled.
scriptRunOnStart = append(scriptRunOnStart, true)
scriptRunOnStop = append(scriptRunOnStop, false)
}

_, err = db.InsertWorkspaceAgentDevcontainers(ctx, database.InsertWorkspaceAgentDevcontainersParams{
WorkspaceAgentID: agentID,
CreatedAt: dbtime.Now(),
ID: devcontainerIDs,
Name: devcontainerNames,
WorkspaceFolder: devcontainerWorkspaceFolders,
ConfigPath: devcontainerConfigPaths,
})
if err != nil {
return xerrors.Errorf("insert agent devcontainer: %w", err)
}
}

_, err = db.InsertWorkspaceAgentLogSources(ctx, database.InsertWorkspaceAgentLogSourcesParams{
Expand Down
Loading