mirror of
https://github.com/coder/coder.git
synced 2026-06-03 21:18:24 +00:00
fca99174ad
This change adds support for sub agent creation and injection into dev containers. Updates coder/internal#621
122 lines
4.5 KiB
Go
122 lines
4.5 KiB
Go
package agentcontainers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"cdr.dev/slog"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
const (
|
|
// DevcontainerLocalFolderLabel is the label that contains the path to
|
|
// the local workspace folder for a devcontainer.
|
|
DevcontainerLocalFolderLabel = "devcontainer.local_folder"
|
|
// DevcontainerConfigFileLabel is the label that contains the path to
|
|
// the devcontainer.json configuration file.
|
|
DevcontainerConfigFileLabel = "devcontainer.config_file"
|
|
// The default workspace folder inside the devcontainer.
|
|
DevcontainerDefaultContainerWorkspaceFolder = "/workspaces"
|
|
)
|
|
|
|
const devcontainerUpScriptTemplate = `
|
|
if ! which devcontainer > /dev/null 2>&1; then
|
|
echo "ERROR: Unable to start devcontainer, @devcontainers/cli is not installed or not found in \$PATH." 1>&2
|
|
echo "Please install @devcontainers/cli by running \"npm install -g @devcontainers/cli\" or by using the \"devcontainers-cli\" Coder module." 1>&2
|
|
exit 1
|
|
fi
|
|
devcontainer up %s
|
|
`
|
|
|
|
// ExtractAndInitializeDevcontainerScripts extracts devcontainer scripts from
|
|
// the given scripts and devcontainers. The devcontainer scripts are removed
|
|
// from the returned scripts so that they can be run separately.
|
|
//
|
|
// Dev Containers have an inherent dependency on start scripts, since they
|
|
// initialize the workspace (e.g. git clone, npm install, etc). This is
|
|
// important if e.g. a Coder module to install @devcontainer/cli is used.
|
|
func ExtractAndInitializeDevcontainerScripts(
|
|
devcontainers []codersdk.WorkspaceAgentDevcontainer,
|
|
scripts []codersdk.WorkspaceAgentScript,
|
|
) (filteredScripts []codersdk.WorkspaceAgentScript, devcontainerScripts []codersdk.WorkspaceAgentScript) {
|
|
ScriptLoop:
|
|
for _, script := range scripts {
|
|
for _, dc := range devcontainers {
|
|
// The devcontainer scripts match the devcontainer ID for
|
|
// identification.
|
|
if script.ID == dc.ID {
|
|
devcontainerScripts = append(devcontainerScripts, devcontainerStartupScript(dc, script))
|
|
continue ScriptLoop
|
|
}
|
|
}
|
|
|
|
filteredScripts = append(filteredScripts, script)
|
|
}
|
|
|
|
return filteredScripts, devcontainerScripts
|
|
}
|
|
|
|
func devcontainerStartupScript(dc codersdk.WorkspaceAgentDevcontainer, script codersdk.WorkspaceAgentScript) codersdk.WorkspaceAgentScript {
|
|
args := []string{
|
|
"--log-format json",
|
|
fmt.Sprintf("--workspace-folder %q", dc.WorkspaceFolder),
|
|
}
|
|
if dc.ConfigPath != "" {
|
|
args = append(args, fmt.Sprintf("--config %q", dc.ConfigPath))
|
|
}
|
|
cmd := fmt.Sprintf(devcontainerUpScriptTemplate, strings.Join(args, " "))
|
|
// Force the script to run in /bin/sh, since some shells (e.g. fish)
|
|
// don't support the script.
|
|
script.Script = fmt.Sprintf("/bin/sh -c '%s'", cmd)
|
|
// Disable RunOnStart, scripts have this set so that when devcontainers
|
|
// have not been enabled, a warning will be surfaced in the agent logs.
|
|
script.RunOnStart = false
|
|
return script
|
|
}
|
|
|
|
// ExpandAllDevcontainerPaths expands all devcontainer paths in the given
|
|
// devcontainers. This is required by the devcontainer CLI, which requires
|
|
// absolute paths for the workspace folder and config path.
|
|
func ExpandAllDevcontainerPaths(logger slog.Logger, expandPath func(string) (string, error), devcontainers []codersdk.WorkspaceAgentDevcontainer) []codersdk.WorkspaceAgentDevcontainer {
|
|
expanded := make([]codersdk.WorkspaceAgentDevcontainer, 0, len(devcontainers))
|
|
for _, dc := range devcontainers {
|
|
expanded = append(expanded, expandDevcontainerPaths(logger, expandPath, dc))
|
|
}
|
|
return expanded
|
|
}
|
|
|
|
func expandDevcontainerPaths(logger slog.Logger, expandPath func(string) (string, error), dc codersdk.WorkspaceAgentDevcontainer) codersdk.WorkspaceAgentDevcontainer {
|
|
logger = logger.With(slog.F("devcontainer", dc.Name), slog.F("workspace_folder", dc.WorkspaceFolder), slog.F("config_path", dc.ConfigPath))
|
|
|
|
if wf, err := expandPath(dc.WorkspaceFolder); err != nil {
|
|
logger.Warn(context.Background(), "expand devcontainer workspace folder failed", slog.Error(err))
|
|
} else {
|
|
dc.WorkspaceFolder = wf
|
|
}
|
|
if dc.ConfigPath != "" {
|
|
// Let expandPath handle home directory, otherwise assume relative to
|
|
// workspace folder or absolute.
|
|
if dc.ConfigPath[0] == '~' {
|
|
if cp, err := expandPath(dc.ConfigPath); err != nil {
|
|
logger.Warn(context.Background(), "expand devcontainer config path failed", slog.Error(err))
|
|
} else {
|
|
dc.ConfigPath = cp
|
|
}
|
|
} else {
|
|
dc.ConfigPath = relativePathToAbs(dc.WorkspaceFolder, dc.ConfigPath)
|
|
}
|
|
}
|
|
return dc
|
|
}
|
|
|
|
func relativePathToAbs(workdir, path string) string {
|
|
path = os.ExpandEnv(path)
|
|
if !filepath.IsAbs(path) {
|
|
path = filepath.Join(workdir, path)
|
|
}
|
|
return path
|
|
}
|