mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
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
101 lines
2.4 KiB
Go
101 lines
2.4 KiB
Go
package testutil
|
|
|
|
import (
|
|
"archive/tar"
|
|
"archive/zip"
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/archive"
|
|
)
|
|
|
|
// Creates an in-memory tar of the files provided.
|
|
// Files in the archive are written with nobody
|
|
// owner/group, and -rw-rw-rw- permissions.
|
|
func CreateTar(t testing.TB, files map[string]string) []byte {
|
|
var buffer bytes.Buffer
|
|
writer := tar.NewWriter(&buffer)
|
|
// Keep track of directories previously added.
|
|
addedDirs := make(map[string]bool)
|
|
for path, content := range files {
|
|
// Add parent directories if they don't exist
|
|
dir := filepath.Dir(path)
|
|
if dir != "." && !addedDirs[dir] {
|
|
err := writer.WriteHeader(&tar.Header{
|
|
Name: dir + "/", // Directory names must end with /
|
|
Mode: 0o755,
|
|
Typeflag: tar.TypeDir,
|
|
})
|
|
require.NoError(t, err)
|
|
addedDirs[dir] = true
|
|
}
|
|
|
|
err := writer.WriteHeader(&tar.Header{
|
|
Name: path,
|
|
Size: int64(len(content)),
|
|
Uid: 65534, // nobody
|
|
Gid: 65534, // nogroup
|
|
Mode: 0o666, // -rw-rw-rw-
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = writer.Write([]byte(content))
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
err := writer.Flush()
|
|
require.NoError(t, err)
|
|
return buffer.Bytes()
|
|
}
|
|
|
|
// Creates an in-memory zip of the files provided.
|
|
// Uses archive.CreateZipFromTar under the hood.
|
|
func CreateZip(t testing.TB, files map[string]string) []byte {
|
|
ta := CreateTar(t, files)
|
|
tr := tar.NewReader(bytes.NewReader(ta))
|
|
za, err := archive.CreateZipFromTar(tr, int64(len(ta)))
|
|
require.NoError(t, err)
|
|
return za
|
|
}
|
|
|
|
// Reads every entry of the in-memory tar into a map keyed by entry name.
|
|
func ReadTar(t testing.TB, data []byte) map[string][]byte {
|
|
t.Helper()
|
|
|
|
entries := make(map[string][]byte)
|
|
tr := tar.NewReader(bytes.NewReader(data))
|
|
for {
|
|
hdr, err := tr.Next()
|
|
if errors.Is(err, io.EOF) {
|
|
return entries
|
|
}
|
|
require.NoError(t, err)
|
|
content, err := io.ReadAll(tr)
|
|
require.NoError(t, err)
|
|
entries[hdr.Name] = content
|
|
}
|
|
}
|
|
|
|
// Reads every entry of the in-memory zip into a map keyed by entry name.
|
|
func ReadZip(t testing.TB, data []byte) map[string][]byte {
|
|
t.Helper()
|
|
|
|
zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
|
require.NoError(t, err)
|
|
entries := make(map[string][]byte, len(zr.File))
|
|
for _, file := range zr.File {
|
|
rc, err := file.Open()
|
|
require.NoError(t, err)
|
|
content, err := io.ReadAll(rc)
|
|
_ = rc.Close()
|
|
require.NoError(t, err)
|
|
entries[file.Name] = content
|
|
}
|
|
return entries
|
|
}
|