Files
coder/agent/agentfiles/api.go
T
Ehab Younes 35ade9e3d2 feat: collect workspace logs in support bundles (#26694)
Add workspace-side file collection to `coder support bundle` via
repeatable --workspace-file flags. The agent resolves the requested
paths or globs inside the remote workspace and streams back a tar with
a manifest and the collected files; nothing is read from the machine
running the command.

- Add POST /api/v0/bundle-files to the agent's agentfiles package.
- Expand env vars in the agent's environment; paths must then be
  absolute or start with ~/ (the agent user's home directory).
- Support ** globs and tail oversized files.
- Record requested patterns, per-path errors, truncation, and the
  applied limits in a manifest.
- Unpack the archive into the bundle under agent/workspace_files/,
  recording dropped entries in collection_errors.txt.
- Write a manifest-only archive marking collection as unsupported for
  agents that predate the endpoint.
- Bound collection: 64 KB request body, 10000 files, 10 MiB per file,
  100 MiB total including archive overhead, 110 MiB client-side read
  cap, 5 minute timeout.

Closes #26020
2026-07-16 13:00:32 +03:00

69 lines
1.8 KiB
Go

package agentfiles
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/spf13/afero"
"cdr.dev/slog/v3"
"github.com/coder/coder/v2/agent/agentgit"
"github.com/coder/coder/v2/agent/usershell"
"github.com/coder/coder/v2/codersdk/workspacesdk"
)
// API exposes file-related operations performed through the agent.
type API struct {
logger slog.Logger
filesystem afero.Fs
pathStore *agentgit.PathStore
envInfo usershell.EnvInfoer
bundleFilesLimits workspacesdk.BundleFilesLimits
}
// Option configures the API.
type Option func(*API)
// WithBundleFilesLimits overrides the bundle files collection limits.
func WithBundleFilesLimits(limits workspacesdk.BundleFilesLimits) Option {
return func(api *API) {
api.bundleFilesLimits = limits
}
}
// WithEnvInfo overrides how the agent user's home directory is resolved.
func WithEnvInfo(envInfo usershell.EnvInfoer) Option {
return func(api *API) {
api.envInfo = envInfo
}
}
func NewAPI(logger slog.Logger, filesystem afero.Fs, pathStore *agentgit.PathStore, opts ...Option) *API {
api := &API{
logger: logger,
filesystem: filesystem,
pathStore: pathStore,
envInfo: usershell.SystemEnvInfo{},
bundleFilesLimits: defaultBundleFilesLimits,
}
for _, opt := range opts {
opt(api)
}
return api
}
// Routes returns the HTTP handler for file-related routes.
func (api *API) Routes() http.Handler {
r := chi.NewRouter()
r.Post("/list-directory", api.HandleLS)
r.Get("/resolve-path", api.HandleResolvePath)
r.Get("/read-file", api.HandleReadFile)
r.Get("/read-file-lines", api.HandleReadFileLines)
r.Post("/write-file", api.HandleWriteFile)
r.Post("/edit-files", api.HandleEditFiles)
r.Post("/bundle-files", api.HandleBundleFiles)
return r
}