Skip to content

Commit a4268ea

Browse files
committed
move /external-agent/{agent}/credentials endpoint to enterprise
1 parent c627aa1 commit a4268ea

File tree

13 files changed

+214
-187
lines changed

13 files changed

+214
-187
lines changed

coderd/apidoc/docs.go

Lines changed: 1 addition & 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: 1 addition & 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: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,9 +1430,6 @@ func New(options *Options) *API {
14301430
r.Post("/", api.postWorkspaceAgentPortShare)
14311431
r.Delete("/", api.deleteWorkspaceAgentPortShare)
14321432
})
1433-
r.Route("/external-agent", func(r chi.Router) {
1434-
r.Get("/{agent}/credentials", api.workspaceExternalAgentCredentials)
1435-
})
14361433
r.Get("/timings", api.workspaceTimings)
14371434
r.Route("/acl", func(r chi.Router) {
14381435
r.Use(

coderd/workspaceagents.go

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,71 +2185,3 @@ func convertWorkspaceAgentLog(logEntry database.WorkspaceAgentLog) codersdk.Work
21852185
SourceID: logEntry.LogSourceID,
21862186
}
21872187
}
2188-
2189-
// @Summary Get workspace external agent credentials
2190-
// @ID get-workspace-external-agent-credentials
2191-
// @Security CoderSessionToken
2192-
// @Produce json
2193-
// @Tags Agents
2194-
// @Param workspace path string true "Workspace ID" format(uuid)
2195-
// @Param agent path string true "Agent name"
2196-
// @Success 200 {object} codersdk.ExternalAgentCredentials
2197-
// @Router /workspaces/{workspace}/external-agent/{agent}/credentials [get]
2198-
func (api *API) workspaceExternalAgentCredentials(rw http.ResponseWriter, r *http.Request) {
2199-
ctx := r.Context()
2200-
workspace := httpmw.WorkspaceParam(r)
2201-
agentName := chi.URLParam(r, "agent")
2202-
2203-
build, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
2204-
if err != nil {
2205-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
2206-
Message: "Failed to get latest workspace build.",
2207-
Detail: err.Error(),
2208-
})
2209-
return
2210-
}
2211-
2212-
agents, err := api.Database.GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx, database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams{
2213-
WorkspaceID: workspace.ID,
2214-
BuildNumber: build.BuildNumber,
2215-
})
2216-
if err != nil {
2217-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
2218-
Message: "Failed to get workspace agents.",
2219-
Detail: err.Error(),
2220-
})
2221-
return
2222-
}
2223-
2224-
var agent *database.WorkspaceAgent
2225-
for i := range agents {
2226-
if agents[i].Name == agentName {
2227-
agent = &agents[i]
2228-
break
2229-
}
2230-
}
2231-
if agent == nil {
2232-
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
2233-
Message: fmt.Sprintf("External agent '%s' not found in workspace.", agentName),
2234-
})
2235-
return
2236-
}
2237-
2238-
if agent.AuthInstanceID.Valid {
2239-
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
2240-
Message: "External agent is authenticated with an instance ID.",
2241-
})
2242-
return
2243-
}
2244-
2245-
initScriptURL := fmt.Sprintf("%s/api/v2/init-script/%s/%s", api.AccessURL.String(), agent.OperatingSystem, agent.Architecture)
2246-
command := fmt.Sprintf("CODER_AGENT_TOKEN=%q curl -fsSL %q | sh", agent.AuthToken.String(), initScriptURL)
2247-
if agent.OperatingSystem == "windows" {
2248-
command = fmt.Sprintf("$env:CODER_AGENT_TOKEN=%q; iwr -useb %q | iex", agent.AuthToken.String(), initScriptURL)
2249-
}
2250-
2251-
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ExternalAgentCredentials{
2252-
AgentToken: agent.AuthToken.String(),
2253-
Command: command,
2254-
})
2255-
}

coderd/workspaceagents_test.go

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -3056,77 +3056,3 @@ func (p *pubsubReinitSpy) Subscribe(event string, listener pubsub.Listener) (can
30563056
p.Unlock()
30573057
return cancel, err
30583058
}
3059-
3060-
func TestWorkspaceExternalAgentCredentials(t *testing.T) {
3061-
t.Parallel()
3062-
client, db := coderdtest.NewWithDatabase(t, nil)
3063-
user := coderdtest.CreateFirstUser(t, client)
3064-
3065-
t.Run("Success - linux", func(t *testing.T) {
3066-
t.Parallel()
3067-
ctx := testutil.Context(t, testutil.WaitShort)
3068-
3069-
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
3070-
OrganizationID: user.OrganizationID,
3071-
OwnerID: user.UserID,
3072-
}).WithAgent(func(a []*proto.Agent) []*proto.Agent {
3073-
a[0].Name = "test-agent"
3074-
a[0].OperatingSystem = "linux"
3075-
a[0].Architecture = "amd64"
3076-
return a
3077-
}).Do()
3078-
3079-
credentials, err := client.WorkspaceExternalAgentCredentials(
3080-
ctx, r.Workspace.ID, "test-agent")
3081-
require.NoError(t, err)
3082-
3083-
require.Equal(t, r.AgentToken, credentials.AgentToken)
3084-
expectedCommand := fmt.Sprintf("CODER_AGENT_TOKEN=%q curl -fsSL \"%s/api/v2/init-script/linux/amd64\" | sh", r.AgentToken, client.URL)
3085-
require.Equal(t, expectedCommand, credentials.Command)
3086-
})
3087-
3088-
t.Run("Success - windows", func(t *testing.T) {
3089-
t.Parallel()
3090-
ctx := testutil.Context(t, testutil.WaitShort)
3091-
3092-
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
3093-
OrganizationID: user.OrganizationID,
3094-
OwnerID: user.UserID,
3095-
}).WithAgent(func(a []*proto.Agent) []*proto.Agent {
3096-
a[0].Name = "test-agent"
3097-
a[0].OperatingSystem = "windows"
3098-
a[0].Architecture = "amd64"
3099-
return a
3100-
}).Do()
3101-
3102-
credentials, err := client.WorkspaceExternalAgentCredentials(
3103-
ctx, r.Workspace.ID, "test-agent")
3104-
require.NoError(t, err)
3105-
3106-
require.Equal(t, r.AgentToken, credentials.AgentToken)
3107-
expectedCommand := fmt.Sprintf("$env:CODER_AGENT_TOKEN=%q; iwr -useb \"%s/api/v2/init-script/windows/amd64\" | iex", r.AgentToken, client.URL)
3108-
require.Equal(t, expectedCommand, credentials.Command)
3109-
})
3110-
3111-
t.Run("WithInstanceID - should return 404", func(t *testing.T) {
3112-
t.Parallel()
3113-
ctx := testutil.Context(t, testutil.WaitShort)
3114-
3115-
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
3116-
OrganizationID: user.OrganizationID,
3117-
OwnerID: user.UserID,
3118-
}).WithAgent(func(a []*proto.Agent) []*proto.Agent {
3119-
a[0].Name = "test-agent"
3120-
a[0].Auth = &proto.Agent_InstanceId{
3121-
InstanceId: uuid.New().String(),
3122-
}
3123-
return a
3124-
}).Do()
3125-
3126-
_, err := client.WorkspaceExternalAgentCredentials(ctx, r.Workspace.ID, "test-agent")
3127-
require.Error(t, err)
3128-
var apiErr *codersdk.Error
3129-
require.ErrorAs(t, err, &apiErr)
3130-
require.Equal(t, "External agent is authenticated with an instance ID.", apiErr.Message)
3131-
})
3132-
}

codersdk/deployment.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ const (
8888
// ManagedAgentLimit is a usage period feature, so the value in the license
8989
// contains both a soft and hard limit. Refer to
9090
// enterprise/coderd/license/license.go for the license format.
91-
FeatureManagedAgentLimit FeatureName = "managed_agent_limit"
91+
FeatureManagedAgentLimit FeatureName = "managed_agent_limit"
92+
FeatureWorkspaceExternalAgent FeatureName = "workspace_external_agent"
9293
)
9394

9495
var (
@@ -115,6 +116,7 @@ var (
115116
FeatureMultipleOrganizations,
116117
FeatureWorkspacePrebuilds,
117118
FeatureManagedAgentLimit,
119+
FeatureWorkspaceExternalAgent,
118120
}
119121

120122
// FeatureNamesMap is a map of all feature names for quick lookups.
@@ -155,6 +157,7 @@ func (n FeatureName) AlwaysEnable() bool {
155157
FeatureCustomRoles: true,
156158
FeatureMultipleOrganizations: true,
157159
FeatureWorkspacePrebuilds: true,
160+
FeatureWorkspaceExternalAgent: true,
158161
}[n]
159162
}
160163

docs/reference/api/agents.md

Lines changed: 0 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/api/enterprise.md

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enterprise/coderd/coderd.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,15 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
506506
apiKeyMiddleware,
507507
httpmw.ExtractNotificationTemplateParam(options.Database),
508508
).Put("/notifications/templates/{notification_template}/method", api.updateNotificationTemplateMethod)
509+
510+
r.Route("/workspaces/{workspace}/external-agent", func(r chi.Router) {
511+
r.Use(
512+
apiKeyMiddleware,
513+
httpmw.ExtractWorkspaceParam(options.Database),
514+
api.RequireFeatureMW(codersdk.FeatureWorkspaceExternalAgent),
515+
)
516+
r.Get("/{agent}/credentials", api.workspaceExternalAgentCredentials)
517+
})
509518
})
510519

511520
if len(options.SCIMAPIKey) != 0 {

enterprise/coderd/workspaceagents.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ package coderd
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
67

8+
"github.com/go-chi/chi/v5"
9+
10+
"github.com/coder/coder/v2/coderd/database"
711
"github.com/coder/coder/v2/coderd/httpapi"
12+
"github.com/coder/coder/v2/coderd/httpmw"
813
"github.com/coder/coder/v2/codersdk"
914
)
1015

@@ -17,3 +22,71 @@ func (api *API) shouldBlockNonBrowserConnections(rw http.ResponseWriter) bool {
1722
}
1823
return false
1924
}
25+
26+
// @Summary Get workspace external agent credentials
27+
// @ID get-workspace-external-agent-credentials
28+
// @Security CoderSessionToken
29+
// @Produce json
30+
// @Tags Enterprise
31+
// @Param workspace path string true "Workspace ID" format(uuid)
32+
// @Param agent path string true "Agent name"
33+
// @Success 200 {object} codersdk.ExternalAgentCredentials
34+
// @Router /workspaces/{workspace}/external-agent/{agent}/credentials [get]
35+
func (api *API) workspaceExternalAgentCredentials(rw http.ResponseWriter, r *http.Request) {
36+
ctx := r.Context()
37+
workspace := httpmw.WorkspaceParam(r)
38+
agentName := chi.URLParam(r, "agent")
39+
40+
build, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
41+
if err != nil {
42+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
43+
Message: "Failed to get latest workspace build.",
44+
Detail: err.Error(),
45+
})
46+
return
47+
}
48+
49+
agents, err := api.Database.GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx, database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams{
50+
WorkspaceID: workspace.ID,
51+
BuildNumber: build.BuildNumber,
52+
})
53+
if err != nil {
54+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
55+
Message: "Failed to get workspace agents.",
56+
Detail: err.Error(),
57+
})
58+
return
59+
}
60+
61+
var agent *database.WorkspaceAgent
62+
for i := range agents {
63+
if agents[i].Name == agentName {
64+
agent = &agents[i]
65+
break
66+
}
67+
}
68+
if agent == nil {
69+
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
70+
Message: fmt.Sprintf("External agent '%s' not found in workspace.", agentName),
71+
})
72+
return
73+
}
74+
75+
if agent.AuthInstanceID.Valid {
76+
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
77+
Message: "External agent is authenticated with an instance ID.",
78+
})
79+
return
80+
}
81+
82+
initScriptURL := fmt.Sprintf("%s/api/v2/init-script/%s/%s", api.AccessURL.String(), agent.OperatingSystem, agent.Architecture)
83+
command := fmt.Sprintf("CODER_AGENT_TOKEN=%q curl -fsSL %q | sh", agent.AuthToken.String(), initScriptURL)
84+
if agent.OperatingSystem == "windows" {
85+
command = fmt.Sprintf("$env:CODER_AGENT_TOKEN=%q; iwr -useb %q | iex", agent.AuthToken.String(), initScriptURL)
86+
}
87+
88+
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ExternalAgentCredentials{
89+
AgentToken: agent.AuthToken.String(),
90+
Command: command,
91+
})
92+
}

0 commit comments

Comments
 (0)