Files
coder/enterprise/coderd/workspaceagents.go
T
Kacper Sawicki 9edceef0bf feat(coderd): add support for external agents to API's and provisioner (#19286)
This pull request introduces support for external workspace management, allowing users to register and manage workspaces that are provisioned and managed outside of the Coder.

Depends on: https://github.com/coder/terraform-provider-coder/pull/424

* GET /api/v2/init-script - Gets the agent initialization script
  * By default, it returns a script for Linux (amd64), but with query parameters (os and arch) you can get the init script for different platforms
* GET /api/v2/workspaces/{workspace}/external-agent/{agent}/credentials - Gets credentials for an external agent **(enterprise)**
* Updated queries to filter workspaces/templates by the has_external_agent field
2025-08-19 10:41:33 +02:00

99 lines
3.0 KiB
Go

package coderd
import (
"context"
"fmt"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/codersdk"
)
func (api *API) shouldBlockNonBrowserConnections(rw http.ResponseWriter) bool {
if api.Entitlements.Enabled(codersdk.FeatureBrowserOnly) {
httpapi.Write(context.Background(), rw, http.StatusConflict, codersdk.Response{
Message: "Non-browser connections are disabled for your deployment.",
})
return true
}
return false
}
// @Summary Get workspace external agent credentials
// @ID get-workspace-external-agent-credentials
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param workspace path string true "Workspace ID" format(uuid)
// @Param agent path string true "Agent name"
// @Success 200 {object} codersdk.ExternalAgentCredentials
// @Router /workspaces/{workspace}/external-agent/{agent}/credentials [get]
func (api *API) workspaceExternalAgentCredentials(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
workspace := httpmw.WorkspaceParam(r)
agentName := chi.URLParam(r, "agent")
build, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to get latest workspace build.",
Detail: err.Error(),
})
return
}
if !build.HasExternalAgent.Bool {
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: "Workspace does not have an external agent.",
})
return
}
agents, err := api.Database.GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx, database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams{
WorkspaceID: workspace.ID,
BuildNumber: build.BuildNumber,
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Failed to get workspace agents.",
Detail: err.Error(),
})
return
}
var agent *database.WorkspaceAgent
for i := range agents {
if agents[i].Name == agentName {
agent = &agents[i]
break
}
}
if agent == nil {
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: fmt.Sprintf("External agent '%s' not found in workspace.", agentName),
})
return
}
if agent.AuthInstanceID.Valid {
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
Message: "External agent is authenticated with an instance ID.",
})
return
}
initScriptURL := fmt.Sprintf("%s/api/v2/init-script/%s/%s", api.AccessURL.String(), agent.OperatingSystem, agent.Architecture)
command := fmt.Sprintf("CODER_AGENT_TOKEN=%q curl -fsSL %q | sh", agent.AuthToken.String(), initScriptURL)
if agent.OperatingSystem == "windows" {
command = fmt.Sprintf("$env:CODER_AGENT_TOKEN=%q; iwr -useb %q | iex", agent.AuthToken.String(), initScriptURL)
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ExternalAgentCredentials{
AgentToken: agent.AuthToken.String(),
Command: command,
})
}