Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(agent/agentcontainers): support apps for dev container agents#18346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
DanielleMaywood wants to merge5 commits intomain
base:main
Choose a base branch
Loading
fromdm-sub-agent-apps-configuration-2
Draft
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletionsagent/agentcontainers/acmock/acmock.go
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

34 changes: 32 additions & 2 deletionsagent/agentcontainers/api.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -63,6 +63,9 @@ type API struct {
subAgentURL string
subAgentEnv []string

userName string
workspaceName string

mu sync.RWMutex
closed bool
containers codersdk.WorkspaceAgentListContainersResponse // Output from the last list operation.
Expand DownExpand Up@@ -151,6 +154,20 @@ func WithSubAgentEnv(env ...string) Option {
}
}

// WithWorkspaceName sets the workspace name for the sub-agent.
func WithWorkspaceName(name string) Option {
return func(api *API) {
api.workspaceName = name
}
}

// WithUserName sets the user name for the sub-agent.
func WithUserName(name string) Option {
return func(api *API) {
api.userName = name
}
}

// WithDevcontainers sets the known devcontainers for the API. This
// allows the API to be aware of devcontainers defined in the workspace
// agent manifest.
Expand DownExpand Up@@ -1109,8 +1126,18 @@ func (api *API) injectSubAgentIntoContainerLocked(ctx context.Context, dc coders
codersdk.DisplayAppSSH: true,
codersdk.DisplayAppPortForward: true,
}

if config, err := api.dccli.ReadConfig(ctx, dc.WorkspaceFolder, dc.ConfigPath); err != nil {
var apps []SubAgentApp

if config, err := api.dccli.ReadConfig(ctx,
dc.WorkspaceFolder,
dc.ConfigPath,
[]string{
fmt.Sprintf("CODER_WORKSPACE_AGENT_NAME=%s", dc.Name),
fmt.Sprintf("CODER_WORKSPACE_OWNER_NAME=%s", api.userName),
fmt.Sprintf("CODER_WORKSPACE_NAME=%s", api.workspaceName),
fmt.Sprintf("CODER_DEPLOYMENT_URL=%s", api.subAgentURL),
},
); err != nil {
api.logger.Error(ctx, "unable to read devcontainer config", slog.Error(err))
} else {
coderCustomization := config.MergedConfiguration.Customizations.Coder
Expand All@@ -1119,6 +1146,8 @@ func (api *API) injectSubAgentIntoContainerLocked(ctx context.Context, dc coders
for app, enabled := range customization.DisplayApps {
displayAppsMap[app] = enabled
}

apps = append(apps, customization.Apps...)
}
}

Expand All@@ -1137,6 +1166,7 @@ func (api *API) injectSubAgentIntoContainerLocked(ctx context.Context, dc coders
OperatingSystem: "linux", // Assuming Linux for dev containers.
Architecture: arch,
DisplayApps: displayApps,
Apps: apps,
})
if err != nil {
return xerrors.Errorf("create agent: %w", err)
Expand Down
97 changes: 92 additions & 5 deletionsagent/agentcontainers/api_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@ import (
"github.com/coder/coder/v2/agent/agentcontainers"
"github.com/coder/coder/v2/agent/agentcontainers/acmock"
"github.com/coder/coder/v2/agent/agentcontainers/watcher"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
"github.com/coder/quartz"
Expand DownExpand Up@@ -67,7 +68,7 @@ type fakeDevcontainerCLI struct {
execErrC chan func(cmd string, args ...string) error // If set, send fn to return err, nil or close to return execErr.
readConfig agentcontainers.DevcontainerConfig
readConfigErr error
readConfigErrC chan error
readConfigErrC chanfunc(envs []string) (agentcontainers.DevcontainerConfig,error)
}

func (f *fakeDevcontainerCLI) Up(ctx context.Context, _, _ string, _ ...agentcontainers.DevcontainerCLIUpOptions) (string, error) {
Expand DownExpand Up@@ -98,14 +99,14 @@ func (f *fakeDevcontainerCLI) Exec(ctx context.Context, _, _ string, cmd string,
return f.execErr
}

func (f *fakeDevcontainerCLI) ReadConfig(ctx context.Context, _, _ string, _ ...agentcontainers.DevcontainerCLIReadConfigOptions) (agentcontainers.DevcontainerConfig, error) {
func (f *fakeDevcontainerCLI) ReadConfig(ctx context.Context, _, _ string,envs []string,_ ...agentcontainers.DevcontainerCLIReadConfigOptions) (agentcontainers.DevcontainerConfig, error) {
if f.readConfigErrC != nil {
select {
case <-ctx.Done():
return agentcontainers.DevcontainerConfig{}, ctx.Err()
caseerr, ok := <-f.readConfigErrC:
casefn, ok := <-f.readConfigErrC:
if ok {
returnf.readConfig, err
returnfn(envs)
}
}
}
Expand DownExpand Up@@ -1268,7 +1269,8 @@ func TestAPI(t *testing.T) {
deleteErrC: make(chan error, 1),
}
fakeDCCLI = &fakeDevcontainerCLI{
execErrC: make(chan func(cmd string, args ...string) error, 1),
execErrC: make(chan func(cmd string, args ...string) error, 1),
readConfigErrC: make(chan func(envs []string) (agentcontainers.DevcontainerConfig, error), 1),
}

testContainer = codersdk.WorkspaceAgentContainer{
Expand DownExpand Up@@ -1307,13 +1309,16 @@ func TestAPI(t *testing.T) {
agentcontainers.WithSubAgentClient(fakeSAC),
agentcontainers.WithSubAgentURL("test-subagent-url"),
agentcontainers.WithDevcontainerCLI(fakeDCCLI),
agentcontainers.WithUserName("test-user"),
agentcontainers.WithWorkspaceName("test-workspace"),
)
defer api.Close()

// Close before api.Close() defer to avoid deadlock after test.
defer close(fakeSAC.createErrC)
defer close(fakeSAC.deleteErrC)
defer close(fakeDCCLI.execErrC)
defer close(fakeDCCLI.readConfigErrC)

// Allow initial agent creation and injection to succeed.
testutil.RequireSend(ctx, t, fakeSAC.createErrC, nil)
Expand All@@ -1322,6 +1327,13 @@ func TestAPI(t *testing.T) {
assert.Empty(t, args)
return nil
}) // Exec pwd.
testutil.RequireSend(ctx, t, fakeDCCLI.readConfigErrC, func(envs []string) (agentcontainers.DevcontainerConfig, error) {
assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=test-container")
assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace")
assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user")
assert.Contains(t, envs, "CODER_DEPLOYMENT_URL=test-subagent-url")
return agentcontainers.DevcontainerConfig{}, nil
})

// Make sure the ticker function has been registered
// before advancing the clock.
Expand DownExpand Up@@ -1374,6 +1386,13 @@ func TestAPI(t *testing.T) {
assert.Empty(t, args)
return nil
}) // Exec pwd.
testutil.RequireSend(ctx, t, fakeDCCLI.readConfigErrC, func(envs []string) (agentcontainers.DevcontainerConfig, error) {
assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=test-container")
assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace")
assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user")
assert.Contains(t, envs, "CODER_DEPLOYMENT_URL=test-subagent-url")
return agentcontainers.DevcontainerConfig{}, nil
})

// Wait until the agent recreation is started.
for len(fakeSAC.createErrC) > 0 {
Expand DownExpand Up@@ -1522,6 +1541,74 @@ func TestAPI(t *testing.T) {
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppPortForward)
},
},
{
name: "WithApps",
customization: []agentcontainers.CoderCustomization{
{
Apps: []agentcontainers.SubAgentApp{
{
Slug: "web-app",
DisplayName: ptr.Ref("Web Application"),
URL: ptr.Ref("http://localhost:8080"),
OpenIn: ptr.Ref(codersdk.WorkspaceAppOpenInTab),
Share: ptr.Ref(codersdk.WorkspaceAppSharingLevelOwner),
Icon: ptr.Ref("/icons/web.svg"),
Order: ptr.Ref(int32(1)),
},
{
Slug: "api-server",
DisplayName: ptr.Ref("API Server"),
URL: ptr.Ref("http://localhost:3000"),
OpenIn: ptr.Ref(codersdk.WorkspaceAppOpenInSlimWindow),
Share: ptr.Ref(codersdk.WorkspaceAppSharingLevelAuthenticated),
Icon: ptr.Ref("/icons/api.svg"),
Order: ptr.Ref(int32(2)),
Hidden: ptr.Ref(true),
},
{
Slug: "docs",
DisplayName: ptr.Ref("Documentation"),
URL: ptr.Ref("http://localhost:4000"),
OpenIn: ptr.Ref(codersdk.WorkspaceAppOpenInTab),
Share: ptr.Ref(codersdk.WorkspaceAppSharingLevelPublic),
Icon: ptr.Ref("/icons/book.svg"),
Order: ptr.Ref(int32(3)),
},
},
},
},
afterCreate: func(t *testing.T, subAgent agentcontainers.SubAgent) {
require.Len(t, subAgent.Apps, 3)

// Verify first app
assert.Equal(t, "web-app", subAgent.Apps[0].Slug)
assert.Equal(t, "Web Application", *subAgent.Apps[0].DisplayName)
assert.Equal(t, "http://localhost:8080", *subAgent.Apps[0].URL)
assert.Equal(t, codersdk.WorkspaceAppOpenInTab, *subAgent.Apps[0].OpenIn)
assert.Equal(t, codersdk.WorkspaceAppSharingLevelOwner, *subAgent.Apps[0].Share)
assert.Equal(t, "/icons/web.svg", *subAgent.Apps[0].Icon)
assert.Equal(t, int32(1), *subAgent.Apps[0].Order)

// Verify second app
assert.Equal(t, "api-server", subAgent.Apps[1].Slug)
assert.Equal(t, "API Server", *subAgent.Apps[1].DisplayName)
assert.Equal(t, "http://localhost:3000", *subAgent.Apps[1].URL)
assert.Equal(t, codersdk.WorkspaceAppOpenInSlimWindow, *subAgent.Apps[1].OpenIn)
assert.Equal(t, codersdk.WorkspaceAppSharingLevelAuthenticated, *subAgent.Apps[1].Share)
assert.Equal(t, "/icons/api.svg", *subAgent.Apps[1].Icon)
assert.Equal(t, int32(2), *subAgent.Apps[1].Order)
assert.Equal(t, true, *subAgent.Apps[1].Hidden)

// Verify third app
assert.Equal(t, "docs", subAgent.Apps[2].Slug)
assert.Equal(t, "Documentation", *subAgent.Apps[2].DisplayName)
assert.Equal(t, "http://localhost:4000", *subAgent.Apps[2].URL)
assert.Equal(t, codersdk.WorkspaceAppOpenInTab, *subAgent.Apps[2].OpenIn)
assert.Equal(t, codersdk.WorkspaceAppSharingLevelPublic, *subAgent.Apps[2].Share)
assert.Equal(t, "/icons/book.svg", *subAgent.Apps[2].Icon)
assert.Equal(t, int32(3), *subAgent.Apps[2].Order)
},
},
}

for _, tt := range tests {
Expand Down
12 changes: 8 additions & 4 deletionsagent/agentcontainers/devcontainercli.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,7 @@ import (
"encoding/json"
"errors"
"io"
"os"

"golang.org/x/xerrors"

Expand All@@ -32,13 +33,14 @@ type DevcontainerCustomizations struct {

type CoderCustomization struct {
DisplayApps map[codersdk.DisplayApp]bool `json:"displayApps,omitempty"`
Apps []SubAgentApp `json:"apps,omitempty"`
}

// DevcontainerCLI is an interface for the devcontainer CLI.
type DevcontainerCLI interface {
Up(ctx context.Context, workspaceFolder, configPath string, opts ...DevcontainerCLIUpOptions) (id string, err error)
Exec(ctx context.Context, workspaceFolder, configPath string, cmd string, cmdArgs []string, opts ...DevcontainerCLIExecOptions) error
ReadConfig(ctx context.Context, workspaceFolder, configPath string, opts ...DevcontainerCLIReadConfigOptions) (DevcontainerConfig, error)
ReadConfig(ctx context.Context, workspaceFolder, configPath string,env []string,opts ...DevcontainerCLIReadConfigOptions) (DevcontainerConfig, error)
}

// DevcontainerCLIUpOptions are options for the devcontainer CLI Up
Expand DownExpand Up@@ -113,8 +115,8 @@ type devcontainerCLIReadConfigConfig struct {
stderr io.Writer
}

//WithExecOutput sets additional stdout and stderr writers for logs
// duringExec operations.
//WithReadConfigOutput sets additional stdout and stderr writers for logs
// duringReadConfig operations.
func WithReadConfigOutput(stdout, stderr io.Writer) DevcontainerCLIReadConfigOptions {
return func(o *devcontainerCLIReadConfigConfig) {
o.stdout = stdout
Expand DownExpand Up@@ -250,7 +252,7 @@ func (d *devcontainerCLI) Exec(ctx context.Context, workspaceFolder, configPath
return nil
}

func (d *devcontainerCLI) ReadConfig(ctx context.Context, workspaceFolder, configPath string, opts ...DevcontainerCLIReadConfigOptions) (DevcontainerConfig, error) {
func (d *devcontainerCLI) ReadConfig(ctx context.Context, workspaceFolder, configPath string,env []string,opts ...DevcontainerCLIReadConfigOptions) (DevcontainerConfig, error) {
conf := applyDevcontainerCLIReadConfigOptions(opts)
logger := d.logger.With(slog.F("workspace_folder", workspaceFolder), slog.F("config_path", configPath))

Expand All@@ -263,6 +265,8 @@ func (d *devcontainerCLI) ReadConfig(ctx context.Context, workspaceFolder, confi
}

c := d.execer.CommandContext(ctx, "devcontainer", args...)
c.Env = append(c.Env, os.Environ()...)
c.Env = append(c.Env, env...)

var stdoutBuf bytes.Buffer
stdoutWriters := []io.Writer{&stdoutBuf, &devcontainerCLILogWriter{ctx: ctx, logger: logger.With(slog.F("stdout", true))}}
Expand Down
2 changes: 1 addition & 1 deletionagent/agentcontainers/devcontainercli_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -316,7 +316,7 @@ func TestDevcontainerCLI_ArgsAndParsing(t *testing.T) {
}

dccli := agentcontainers.NewDevcontainerCLI(logger, testExecer)
config, err := dccli.ReadConfig(ctx, tt.workspaceFolder, tt.configPath, tt.opts...)
config, err := dccli.ReadConfig(ctx, tt.workspaceFolder, tt.configPath,[]string{},tt.opts...)
if tt.wantError {
assert.Error(t, err, "want error")
assert.Equal(t, agentcontainers.DevcontainerConfig{}, config, "expected empty config on error")
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp