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

refactor(agent): update agentcontainers api initialization#17600

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

Merged
mafredri merged 16 commits intomainfrommafredri/fix-agent-agentcontainers-init
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
16 commits
Select commitHold shift + click to select a range
2fd85f7
refactor(agent): update agentcontainers api initialization
mafredriApr 29, 2025
901a258
add warn
mafredriApr 29, 2025
44cc9a6
lint
mafredriApr 29, 2025
ebed3de
refactor, remove control flag
mafredriApr 29, 2025
02d0228
remove noop dccli
mafredriApr 29, 2025
2a9a842
fix agent options
mafredriApr 29, 2025
fd69221
update agentcontainers API file watch init
mafredriApr 29, 2025
17fa155
rename start to ready
mafredriApr 29, 2025
3ebf2d3
update disabled message and use http 403 instead of 404
mafredriApr 29, 2025
9d57279
fix(site): handle containers disabled
mafredriApr 29, 2025
ea9f564
fix copy
mafredriApr 29, 2025
3f9bd1d
fix test
mafredriApr 29, 2025
32b7d18
echo codersdk error from agent
mafredriApr 29, 2025
6580f35
fix test^2
mafredriApr 29, 2025
4a5842b
rename ready to signalready
mafredriApr 29, 2025
1f413db
use safer atomic pointer for containerapi
mafredriApr 29, 2025
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
16 changes: 10 additions & 6 deletionsagent/agent.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -89,9 +89,9 @@ type Options struct {
ServiceBannerRefreshInterval time.Duration
BlockFileTransfer bool
Execer agentexec.Execer
ContainerLister agentcontainers.Lister

ExperimentalDevcontainersEnabled bool
ContainerAPIOptions []agentcontainers.Option // Enable ExperimentalDevcontainersEnabled for these to be effective.
}

type Client interface {
Expand DownExpand Up@@ -154,9 +154,6 @@ func New(options Options) Agent {
if options.Execer == nil {
options.Execer = agentexec.DefaultExecer
}
if options.ContainerLister == nil {
options.ContainerLister = agentcontainers.NoopLister{}
}

hardCtx, hardCancel := context.WithCancel(context.Background())
gracefulCtx, gracefulCancel := context.WithCancel(hardCtx)
Expand DownExpand Up@@ -192,9 +189,9 @@ func New(options Options) Agent {
prometheusRegistry: prometheusRegistry,
metrics: newAgentMetrics(prometheusRegistry),
execer: options.Execer,
lister: options.ContainerLister,

experimentalDevcontainersEnabled: options.ExperimentalDevcontainersEnabled,
containerAPIOptions: options.ContainerAPIOptions,
}
// Initially, we have a closed channel, reflecting the fact that we are not initially connected.
// Each time we connect we replace the channel (while holding the closeMutex) with a new one
Expand DownExpand Up@@ -274,9 +271,10 @@ type agent struct {
// labeled in Coder with the agent + workspace.
metrics *agentMetrics
execer agentexec.Execer
lister agentcontainers.Lister

experimentalDevcontainersEnabled bool
containerAPIOptions []agentcontainers.Option
containerAPI atomic.Pointer[agentcontainers.API] // Set by apiHandler.
}

func (a *agent) TailnetConn() *tailnet.Conn {
Expand DownExpand Up@@ -1170,6 +1168,12 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context,
}
a.metrics.startupScriptSeconds.WithLabelValues(label).Set(dur)
a.scriptRunner.StartCron()
if containerAPI := a.containerAPI.Load(); containerAPI != nil {
// Inform the container API that the agent is ready.
// This allows us to start watching for changes to
// the devcontainer configuration files.
containerAPI.SignalReady()
}
})
if err != nil {
return xerrors.Errorf("track conn goroutine: %w", err)
Expand Down
67 changes: 47 additions & 20 deletionsagent/agentcontainers/api.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,7 @@ type API struct {
watcher watcher.Watcher

cacheDuration time.Duration
execer agentexec.Execer
cl Lister
dccli DevcontainerCLI
clock quartz.Clock
Expand All@@ -56,14 +57,6 @@ type API struct {
// Option is a functional option for API.
type Option func(*API)

// WithLister sets the agentcontainers.Lister implementation to use.
// The default implementation uses the Docker CLI to list containers.
func WithLister(cl Lister) Option {
return func(api *API) {
api.cl = cl
}
}

// WithClock sets the quartz.Clock implementation to use.
// This is primarily used for testing to control time.
func WithClock(clock quartz.Clock) Option {
Expand All@@ -72,6 +65,21 @@ func WithClock(clock quartz.Clock) Option {
}
}

// WithExecer sets the agentexec.Execer implementation to use.
func WithExecer(execer agentexec.Execer) Option {
return func(api *API) {
api.execer = execer
}
}

// WithLister sets the agentcontainers.Lister implementation to use.
// The default implementation uses the Docker CLI to list containers.
func WithLister(cl Lister) Option {
return func(api *API) {
api.cl = cl
}
}

// WithDevcontainerCLI sets the DevcontainerCLI implementation to use.
// This can be used in tests to modify @devcontainer/cli behavior.
func WithDevcontainerCLI(dccli DevcontainerCLI) Option {
Expand DownExpand Up@@ -113,6 +121,7 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
done: make(chan struct{}),
logger: logger,
clock: quartz.NewReal(),
execer: agentexec.DefaultExecer,
cacheDuration: defaultGetContainersCacheDuration,
lockCh: make(chan struct{}, 1),
devcontainerNames: make(map[string]struct{}),
Expand All@@ -123,30 +132,46 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
opt(api)
}
if api.cl == nil {
api.cl =&DockerCLILister{}
api.cl =NewDocker(api.execer)
}
if api.dccli == nil {
api.dccli = NewDevcontainerCLI(logger.Named("devcontainer-cli"),agentexec.DefaultExecer)
api.dccli = NewDevcontainerCLI(logger.Named("devcontainer-cli"),api.execer)
}
if api.watcher == nil {
api.watcher = watcher.NewNoop()
var err error
api.watcher, err = watcher.NewFSNotify()
if err != nil {
logger.Error(ctx, "create file watcher service failed", slog.Error(err))
api.watcher = watcher.NewNoop()
}
}

go api.loop()

return api
}

// SignalReady signals the API that we are ready to begin watching for
// file changes. This is used to prime the cache with the current list
// of containers and to start watching the devcontainer config files for
// changes. It should be called after the agent ready.
func (api *API) SignalReady() {
// Prime the cache with the current list of containers.
_, _ = api.cl.List(api.ctx)

// Make sure we watch the devcontainer config files for changes.
for _, devcontainer := range api.knownDevcontainers {
if devcontainer.ConfigPath != "" {
if err := api.watcher.Add(devcontainer.ConfigPath); err != nil {
api.logger.Error(ctx, "watch devcontainer config file failed", slog.Error(err), slog.F("file", devcontainer.ConfigPath))
}
if devcontainer.ConfigPath == "" {
continue
}
}

go api.start()

return api
if err := api.watcher.Add(devcontainer.ConfigPath); err != nil {
api.logger.Error(api.ctx, "watch devcontainer config file failed", slog.Error(err), slog.F("file", devcontainer.ConfigPath))
}
}
}

func (api *API)start() {
func (api *API)loop() {
defer close(api.done)

for {
Expand DownExpand Up@@ -187,9 +212,11 @@ func (api *API) start() {
// Routes returns the HTTP handler for container-related routes.
func (api *API) Routes() http.Handler {
r := chi.NewRouter()

r.Get("/", api.handleList)
r.Get("/devcontainers", api.handleListDevcontainers)
r.Post("/{id}/recreate", api.handleRecreate)

return r
}

Expand Down
5 changes: 5 additions & 0 deletionsagent/agentcontainers/api_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,6 +18,7 @@ import (
"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/agent/agentcontainers"
"github.com/coder/coder/v2/agent/agentcontainers/watcher"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
"github.com/coder/quartz"
Expand DownExpand Up@@ -253,6 +254,7 @@ func TestAPI(t *testing.T) {
logger,
agentcontainers.WithLister(tt.lister),
agentcontainers.WithDevcontainerCLI(tt.devcontainerCLI),
agentcontainers.WithWatcher(watcher.NewNoop()),
)
defer api.Close()
r.Mount("/", api.Routes())
Expand DownExpand Up@@ -558,6 +560,7 @@ func TestAPI(t *testing.T) {
r := chi.NewRouter()
apiOptions := []agentcontainers.Option{
agentcontainers.WithLister(tt.lister),
agentcontainers.WithWatcher(watcher.NewNoop()),
}

if len(tt.knownDevcontainers) > 0 {
Expand DownExpand Up@@ -631,6 +634,8 @@ func TestAPI(t *testing.T) {
)
defer api.Close()

api.SignalReady()

r := chi.NewRouter()
r.Mount("/", api.Routes())

Expand Down
27 changes: 21 additions & 6 deletionsagent/api.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,23 +37,35 @@ func (a *agent) apiHandler() (http.Handler, func() error) {
cacheDuration: cacheDuration,
}

containerAPIOpts := []agentcontainers.Option{
agentcontainers.WithLister(a.lister),
}
if a.experimentalDevcontainersEnabled {
containerAPIOpts := []agentcontainers.Option{
agentcontainers.WithExecer(a.execer),
}
manifest := a.manifest.Load()
if manifest != nil && len(manifest.Devcontainers) > 0 {
containerAPIOpts = append(
containerAPIOpts,
agentcontainers.WithDevcontainers(manifest.Devcontainers),
)
}

// Append after to allow the agent options to override the default options.
containerAPIOpts = append(containerAPIOpts, a.containerAPIOptions...)

containerAPI := agentcontainers.NewAPI(a.logger.Named("containers"), containerAPIOpts...)
r.Mount("/api/v0/containers", containerAPI.Routes())
a.containerAPI.Store(containerAPI)
} else {
r.HandleFunc("/api/v0/containers", func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(r.Context(), w, http.StatusForbidden, codersdk.Response{
Message: "The agent dev containers feature is experimental and not enabled by default.",
Detail: "To enable this feature, set CODER_AGENT_DEVCONTAINERS_ENABLE=true in your template.",
})
})
}
containerAPI := agentcontainers.NewAPI(a.logger.Named("containers"), containerAPIOpts...)

promHandler := PrometheusMetricsHandler(a.prometheusRegistry, a.logger)

r.Mount("/api/v0/containers", containerAPI.Routes())
r.Get("/api/v0/listening-ports", lp.handler)
r.Get("/api/v0/netcheck", a.HandleNetcheck)
r.Post("/api/v0/list-directory", a.HandleLS)
Expand All@@ -64,7 +76,10 @@ func (a *agent) apiHandler() (http.Handler, func() error) {
r.Get("/debug/prometheus", promHandler.ServeHTTP)

return r, func() error {
return containerAPI.Close()
if containerAPI := a.containerAPI.Load(); containerAPI != nil {
return containerAPI.Close()
}
return nil
}
}

Expand Down
11 changes: 3 additions & 8 deletionscli/agent.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,7 +26,6 @@ import (
"cdr.dev/slog/sloggers/slogjson"
"cdr.dev/slog/sloggers/slogstackdriver"
"github.com/coder/coder/v2/agent"
"github.com/coder/coder/v2/agent/agentcontainers"
"github.com/coder/coder/v2/agent/agentexec"
"github.com/coder/coder/v2/agent/agentssh"
"github.com/coder/coder/v2/agent/reaper"
Expand DownExpand Up@@ -319,13 +318,10 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
return xerrors.Errorf("create agent execer: %w", err)
}

var containerLister agentcontainers.Lister
if !experimentalDevcontainersEnabled {
logger.Info(ctx, "agent devcontainer detection not enabled")
containerLister = &agentcontainers.NoopLister{}
} else {
if experimentalDevcontainersEnabled {
logger.Info(ctx, "agent devcontainer detection enabled")
containerLister = agentcontainers.NewDocker(execer)
} else {
logger.Info(ctx, "agent devcontainer detection not enabled")
}

agnt := agent.New(agent.Options{
Expand DownExpand Up@@ -354,7 +350,6 @@ func (r *RootCmd) workspaceAgent() *serpent.Command {
PrometheusRegistry: prometheusRegistry,
BlockFileTransfer: blockFileTransfer,
Execer: execer,
ContainerLister: containerLister,

ExperimentalDevcontainersEnabled: experimentalDevcontainersEnabled,
})
Expand Down
2 changes: 0 additions & 2 deletionscli/exp_rpty_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,7 +9,6 @@ import (
"github.com/ory/dockertest/v3/docker"

"github.com/coder/coder/v2/agent"
"github.com/coder/coder/v2/agent/agentcontainers"
"github.com/coder/coder/v2/agent/agenttest"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
Expand DownExpand Up@@ -112,7 +111,6 @@ func TestExpRpty(t *testing.T) {

_ = agenttest.New(t, client.URL, agentToken, func(o *agent.Options) {
o.ExperimentalDevcontainersEnabled = true
o.ContainerLister = agentcontainers.NewDocker(o.Execer)
})
_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()

Expand Down
7 changes: 5 additions & 2 deletionscli/open_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,6 +14,7 @@ import (
"go.uber.org/mock/gomock"

"github.com/coder/coder/v2/agent"
"github.com/coder/coder/v2/agent/agentcontainers"
"github.com/coder/coder/v2/agent/agentcontainers/acmock"
"github.com/coder/coder/v2/agent/agenttest"
"github.com/coder/coder/v2/cli/clitest"
Expand DownExpand Up@@ -335,7 +336,8 @@ func TestOpenVSCodeDevContainer(t *testing.T) {
})

_ = agenttest.New(t, client.URL, agentToken, func(o *agent.Options) {
o.ContainerLister = mcl
o.ExperimentalDevcontainersEnabled = true
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithLister(mcl))
})
_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()

Expand DownExpand Up@@ -508,7 +510,8 @@ func TestOpenVSCodeDevContainer_NoAgentDirectory(t *testing.T) {
})

_ = agenttest.New(t, client.URL, agentToken, func(o *agent.Options) {
o.ContainerLister = mcl
o.ExperimentalDevcontainersEnabled = true
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithLister(mcl))
})
_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()

Expand Down
2 changes: 0 additions & 2 deletionscli/ssh.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -299,8 +299,6 @@ func (r *RootCmd) ssh() *serpent.Command {
}
if len(cts.Containers) == 0 {
cliui.Info(inv.Stderr, "No containers found!")
cliui.Info(inv.Stderr, "Tip: Agent container integration is experimental and not enabled by default.")
cliui.Info(inv.Stderr, " To enable it, set CODER_AGENT_DEVCONTAINERS_ENABLE=true in your template.")
return nil
}
var found bool
Expand Down
14 changes: 3 additions & 11 deletionscli/ssh_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2029,7 +2029,6 @@ func TestSSH_Container(t *testing.T) {

_ = agenttest.New(t, client.URL, agentToken, func(o *agent.Options) {
o.ExperimentalDevcontainersEnabled = true
o.ContainerLister = agentcontainers.NewDocker(o.Execer)
})
_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()

Expand DownExpand Up@@ -2058,7 +2057,7 @@ func TestSSH_Container(t *testing.T) {
mLister := acmock.NewMockLister(ctrl)
_ = agenttest.New(t, client.URL, agentToken, func(o *agent.Options) {
o.ExperimentalDevcontainersEnabled = true
o.ContainerLister = mLister
o.ContainerAPIOptions =append(o.ContainerAPIOptions, agentcontainers.WithLister(mLister))
})
_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()

Expand DownExpand Up@@ -2097,16 +2096,9 @@ func TestSSH_Container(t *testing.T) {

inv, root := clitest.New(t, "ssh", workspace.Name, "-c", uuid.NewString())
clitest.SetupConfig(t, client, root)
ptty := ptytest.New(t).Attach(inv)

cmdDone := tGo(t, func() {
err := inv.WithContext(ctx).Run()
assert.NoError(t, err)
})

ptty.ExpectMatch("No containers found!")
ptty.ExpectMatch("Tip: Agent container integration is experimental and not enabled by default.")
<-cmdDone
err := inv.WithContext(ctx).Run()
require.ErrorContains(t, err, "The agent dev containers feature is experimental and not enabled by default.")
})
}

Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp