Skip to content

Commit a17ca58

Browse files
committed
[ci skip] commit wip
1 parent 2648440 commit a17ca58

File tree

6 files changed

+138
-7
lines changed

6 files changed

+138
-7
lines changed

agent/containers.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,12 @@ func (*dockerCLIContainerLister) List(ctx context.Context) ([]codersdk.Workspace
133133

134134
func convertDockerCLIList(in dockerCLIList) codersdk.WorkspaceAgentContainer {
135135
out := codersdk.WorkspaceAgentContainer{
136-
FriendlyName: in.Names,
137-
ID: in.ID,
138-
Image: in.Image,
139-
Labels: map[string]string{},
136+
ID: in.ID,
137+
Image: in.Image,
138+
Labels: map[string]string{},
140139
}
140+
// Remove the leading slash from the container name
141+
out.FriendlyName = strings.TrimPrefix(in.Names, "/")
141142

142143
createdAt, err := time.Parse(dockerCreatedAtTimeFormat, in.CreatedAt)
143144
if err != nil {

coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,7 @@ func New(options *Options) *API {
12121212
r.Get("/listening-ports", api.workspaceAgentListeningPorts)
12131213
r.Get("/connection", api.workspaceAgentConnection)
12141214
r.Get("/coordinate", api.workspaceAgentClientCoordinate)
1215+
r.Get("/containers", api.workspaceAgentListContainers)
12151216

12161217
// PTY is part of workspaceAppServer.
12171218
})

coderd/workspaceagents.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,61 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req
678678
httpapi.Write(ctx, rw, http.StatusOK, portsResponse)
679679
}
680680

681+
func (api *API) workspaceAgentListContainers(rw http.ResponseWriter, r *http.Request) {
682+
ctx := r.Context()
683+
workspaceAgent := httpmw.WorkspaceAgentParam(r)
684+
685+
// If the agent is unreachable, the request will hang. Assume that if we
686+
// don't get a response after 30s that the agent is unreachable.
687+
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
688+
defer cancel()
689+
apiAgent, err := db2sdk.WorkspaceAgent(
690+
api.DERPMap(),
691+
*api.TailnetCoordinator.Load(),
692+
workspaceAgent,
693+
nil,
694+
nil,
695+
nil,
696+
api.AgentInactiveDisconnectTimeout,
697+
api.DeploymentValues.AgentFallbackTroubleshootingURL.String(),
698+
)
699+
if err != nil {
700+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
701+
Message: "Internal error reading workspace agent.",
702+
Detail: err.Error(),
703+
})
704+
return
705+
}
706+
if apiAgent.Status != codersdk.WorkspaceAgentConnected {
707+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
708+
Message: fmt.Sprintf("Agent state is %q, it must be in the %q state.", apiAgent.Status, codersdk.WorkspaceAgentConnected),
709+
})
710+
return
711+
}
712+
713+
agentConn, release, err := api.agentProvider.AgentConn(ctx, workspaceAgent.ID)
714+
if err != nil {
715+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
716+
Message: "Internal error dialing workspace agent.",
717+
Detail: err.Error(),
718+
})
719+
return
720+
}
721+
defer release()
722+
723+
// Get a list of containers that the agent is able to detect
724+
containers, err := agentConn.ListContainers(ctx)
725+
if err != nil {
726+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
727+
Message: "Internal error fetching containers.",
728+
Detail: err.Error(),
729+
})
730+
return
731+
}
732+
733+
httpapi.Write(ctx, rw, http.StatusOK, containers)
734+
}
735+
681736
// @Summary Get connection info for workspace agent
682737
// @ID get-connection-info-for-workspace-agent
683738
// @Security CoderSessionToken

coderd/workspaceagents_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616

1717
"github.com/go-jose/go-jose/v4/jwt"
1818
"github.com/google/uuid"
19+
"github.com/ory/dockertest/v3"
20+
"github.com/ory/dockertest/v3/docker"
1921
"github.com/stretchr/testify/assert"
2022
"github.com/stretchr/testify/require"
2123
"golang.org/x/xerrors"
@@ -1053,6 +1055,61 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
10531055
})
10541056
}
10551057

1058+
func TestWorkspaceAgentContainers(t *testing.T) {
1059+
t.Parallel()
1060+
1061+
pool, err := dockertest.NewPool("")
1062+
require.NoError(t, err, "Could not connect to docker")
1063+
testLabelValue := uuid.New().String()
1064+
ct, err := pool.RunWithOptions(&dockertest.RunOptions{
1065+
Repository: "busybox",
1066+
Tag: "latest",
1067+
Cmd: []string{"sleep", "infnity"},
1068+
Labels: map[string]string{"com.coder.test": testLabelValue},
1069+
}, func(config *docker.HostConfig) {
1070+
config.AutoRemove = true
1071+
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
1072+
})
1073+
require.NoError(t, err, "Could not start test docker container")
1074+
t.Cleanup(func() {
1075+
assert.NoError(t, pool.Purge(ct), "Could not purge resource")
1076+
})
1077+
1078+
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{})
1079+
1080+
user := coderdtest.CreateFirstUser(t, client)
1081+
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
1082+
OrganizationID: user.OrganizationID,
1083+
OwnerID: user.UserID,
1084+
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
1085+
return agents
1086+
}).Do()
1087+
_ = agenttest.New(t, client.URL, r.AgentToken, func(_ *agent.Options) {})
1088+
resources := coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).Wait()
1089+
require.Len(t, resources, 1, "expected one resource")
1090+
require.Len(t, resources[0].Agents, 1, "expected one agent")
1091+
agentID := resources[0].Agents[0].ID
1092+
1093+
ctx := testutil.Context(t, testutil.WaitLong)
1094+
res, err := client.WorkspaceAgentListContainers(ctx, agentID)
1095+
require.NoError(t, err, "failed to list containers")
1096+
require.NotEmpty(t, res.Containers, "expected to find containers")
1097+
1098+
var found bool
1099+
for _, c := range res.Containers {
1100+
if c.ID == ct.Container.ID {
1101+
found = true
1102+
assert.Equal(t, ct.Container.ID, c.ID)
1103+
assert.Equal(t, "busybox:latest", c.Image)
1104+
// The container name is prefixed with a slash.
1105+
assert.Equal(t, strings.TrimPrefix(ct.Container.Name, "/"), c.FriendlyName)
1106+
assert.Equal(t, ct.Container.Config.Labels, c.Labels)
1107+
break
1108+
}
1109+
}
1110+
require.True(t, found, "expected to find container")
1111+
}
1112+
10561113
func TestWorkspaceAgentAppHealth(t *testing.T) {
10571114
t.Parallel()
10581115
client, db := coderdtest.NewWithDatabase(t, nil)

codersdk/workspaceagents.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,10 @@ type WorkspaceAgentContainer struct {
405405
Labels map[string]string `json:"labels"`
406406
}
407407

408+
// WorkspaceAgentListContainersResponse is the response to the list containers
409+
// request.
408410
type WorkspaceAgentListContainersResponse struct {
409411
Containers []WorkspaceAgentContainer `json:"containers"`
410-
Mtime time.Time `json:"mtime"`
411412
}
412413

413414
// WorkspaceAgentListContainers returns a list of containers that are currently
@@ -421,9 +422,9 @@ func (c *Client) WorkspaceAgentListContainers(ctx context.Context, agentID uuid.
421422
if res.StatusCode != http.StatusOK {
422423
return WorkspaceAgentListContainersResponse{}, ReadBodyAsError(res)
423424
}
424-
var resp WorkspaceAgentListContainersResponse
425+
var cr WorkspaceAgentListContainersResponse
425426

426-
return resp, json.NewDecoder(res.Body).Decode(&resp.Containers)
427+
return cr, json.NewDecoder(res.Body).Decode(&cr)
427428
}
428429

429430
//nolint:revive // Follow is a control flag on the server as well.

codersdk/workspacesdk/agentconn.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,22 @@ func (c *AgentConn) PrometheusMetrics(ctx context.Context) ([]byte, error) {
336336
return bs, nil
337337
}
338338

339+
// ListContainers returns a response from the agent's containers endpoint
340+
func (c *AgentConn) ListContainers(ctx context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
341+
ctx, span := tracing.StartSpan(ctx)
342+
defer span.End()
343+
res, err := c.apiRequest(ctx, http.MethodGet, "/api/v0/containers", nil)
344+
if err != nil {
345+
return codersdk.WorkspaceAgentListContainersResponse{}, xerrors.Errorf("do request: %w", err)
346+
}
347+
defer res.Body.Close()
348+
if res.StatusCode != http.StatusOK {
349+
return codersdk.WorkspaceAgentListContainersResponse{}, codersdk.ReadBodyAsError(res)
350+
}
351+
var resp codersdk.WorkspaceAgentListContainersResponse
352+
return resp, json.NewDecoder(res.Body).Decode(&resp.Containers)
353+
}
354+
339355
// apiRequest makes a request to the workspace agent's HTTP API server.
340356
func (c *AgentConn) apiRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
341357
ctx, span := tracing.StartSpan(ctx)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy