Skip to content

Commit 9edceef

Browse files
authored
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: coder/terraform-provider-coder#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
1 parent f085c37 commit 9edceef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1616
-98
lines changed

cli/testdata/coder_list_--output_json.golden

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070
"most_recently_seen": null
7171
},
7272
"template_version_preset_id": null,
73-
"has_ai_task": false
73+
"has_ai_task": false,
74+
"has_external_agent": false
7475
},
7576
"latest_app_status": null,
7677
"outdated": false,

cli/testdata/coder_provisioner_list_--output_json.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"last_seen_at": "====[timestamp]=====",
88
"name": "test-daemon",
99
"version": "v0.0.0-devel",
10-
"api_version": "1.8",
10+
"api_version": "1.9",
1111
"provisioners": [
1212
"echo"
1313
],

coderd/apidoc/docs.go

Lines changed: 93 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 85 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,9 @@ func New(options *Options) *API {
15661566
r.Use(apiKeyMiddleware)
15671567
r.Get("/", api.tailnetRPCConn)
15681568
})
1569+
r.Route("/init-script", func(r chi.Router) {
1570+
r.Get("/{os}/{arch}", api.initScript)
1571+
})
15691572
})
15701573

15711574
if options.SwaggerEndpoint {

coderd/coderdtest/swaggerparser.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,8 @@ func assertSecurityDefined(t *testing.T, comment SwaggerComment) {
310310
comment.router == "/" ||
311311
comment.router == "/users/login" ||
312312
comment.router == "/users/otp/request" ||
313-
comment.router == "/users/otp/change-password" {
313+
comment.router == "/users/otp/change-password" ||
314+
comment.router == "/init-script/{os}/{arch}" {
314315
return // endpoints do not require authorization
315316
}
316317
assert.Containsf(t, authorizedSecurityTags, comment.security, "@Security must be either of these options: %v", authorizedSecurityTags)
@@ -361,7 +362,8 @@ func assertProduce(t *testing.T, comment SwaggerComment) {
361362
(comment.router == "/licenses/{id}" && comment.method == "delete") ||
362363
(comment.router == "/debug/coordinator" && comment.method == "get") ||
363364
(comment.router == "/debug/tailnet" && comment.method == "get") ||
364-
(comment.router == "/workspaces/{workspace}/acl" && comment.method == "patch") {
365+
(comment.router == "/workspaces/{workspace}/acl" && comment.method == "patch") ||
366+
(comment.router == "/init-script/{os}/{arch}" && comment.method == "get") {
365367
return // Exception: HTTP 200 is returned without response entity
366368
}
367369

coderd/entitlements/entitlements.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,9 @@ func (l *Set) Errors() []string {
161161
defer l.entitlementsMu.RUnlock()
162162
return slices.Clone(l.entitlements.Errors)
163163
}
164+
165+
func (l *Set) HasLicense() bool {
166+
l.entitlementsMu.RLock()
167+
defer l.entitlementsMu.RUnlock()
168+
return l.entitlements.HasLicense
169+
}

coderd/initscript.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package coderd
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/base64"
6+
"fmt"
7+
"net/http"
8+
"strings"
9+
10+
"github.com/go-chi/chi/v5"
11+
12+
"github.com/coder/coder/v2/coderd/httpapi"
13+
"github.com/coder/coder/v2/codersdk"
14+
"github.com/coder/coder/v2/provisionersdk"
15+
)
16+
17+
// @Summary Get agent init script
18+
// @ID get-agent-init-script
19+
// @Produce text/plain
20+
// @Tags InitScript
21+
// @Param os path string true "Operating system"
22+
// @Param arch path string true "Architecture"
23+
// @Success 200 "Success"
24+
// @Router /init-script/{os}/{arch} [get]
25+
func (api *API) initScript(rw http.ResponseWriter, r *http.Request) {
26+
os := strings.ToLower(chi.URLParam(r, "os"))
27+
arch := strings.ToLower(chi.URLParam(r, "arch"))
28+
29+
script, exists := provisionersdk.AgentScriptEnv()[fmt.Sprintf("CODER_AGENT_SCRIPT_%s_%s", os, arch)]
30+
if !exists {
31+
httpapi.Write(r.Context(), rw, http.StatusBadRequest, codersdk.Response{
32+
Message: fmt.Sprintf("Unknown os/arch: %s/%s", os, arch),
33+
})
34+
return
35+
}
36+
script = strings.ReplaceAll(script, "${ACCESS_URL}", api.AccessURL.String()+"/")
37+
script = strings.ReplaceAll(script, "${AUTH_TYPE}", "token")
38+
39+
scriptBytes := []byte(script)
40+
hash := sha256.Sum256(scriptBytes)
41+
rw.Header().Set("Content-Digest", fmt.Sprintf("sha256:%x", base64.StdEncoding.EncodeToString(hash[:])))
42+
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
43+
rw.WriteHeader(http.StatusOK)
44+
_, _ = rw.Write(scriptBytes)
45+
}

0 commit comments

Comments
 (0)