Create a CLI-only banger vm run [path] flow that resolves the enclosing git repository, creates a VM, imports a guest checkout, and launches opencode attach automatically from the host. Build the guest checkout by bundling git history plus the resolved base and head commits, cloning that bundle in the guest, and overlaying tracked plus untracked non-ignored files over SSH so local working-tree changes carry over. Support guest-only branch creation with --branch and --from, reject bare repos and submodules, and add selective tar helpers plus CLI seams to keep the workflow testable. Validate with go test ./..., make build, banger vm run --help, and the expected --from requires --branch error path.
142 lines
3.7 KiB
Go
142 lines
3.7 KiB
Go
package guest
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
func TestWriteTarArchiveKeepsTopLevelDirectory(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
sourceDir := filepath.Join(t.TempDir(), "6.8.0-test")
|
|
if err := os.MkdirAll(filepath.Join(sourceDir, "kernel"), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "modules.dep"), []byte("deps"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile modules.dep: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "kernel", "module.ko"), []byte("ko"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile module.ko: %v", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := writeTarArchive(&buf, sourceDir); err != nil {
|
|
t.Fatalf("writeTarArchive: %v", err)
|
|
}
|
|
|
|
tr := tar.NewReader(bytes.NewReader(buf.Bytes()))
|
|
var names []string
|
|
for {
|
|
header, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("tar.Next: %v", err)
|
|
}
|
|
names = append(names, header.Name)
|
|
}
|
|
|
|
want := map[string]struct{}{
|
|
"6.8.0-test": {},
|
|
"6.8.0-test/modules.dep": {},
|
|
"6.8.0-test/kernel": {},
|
|
"6.8.0-test/kernel/module.ko": {},
|
|
}
|
|
if len(names) != len(want) {
|
|
t.Fatalf("archive names = %v, want %d entries", names, len(want))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := want[name]; !ok {
|
|
t.Fatalf("unexpected archive entry %q in %v", name, names)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAuthorizedPublicKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
|
|
if err != nil {
|
|
t.Fatalf("GenerateKey: %v", err)
|
|
}
|
|
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
|
})
|
|
keyPath := filepath.Join(t.TempDir(), "id_rsa")
|
|
if err := os.WriteFile(keyPath, privateKeyPEM, 0o600); err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
}
|
|
|
|
publicKey, err := AuthorizedPublicKey(keyPath)
|
|
if err != nil {
|
|
t.Fatalf("AuthorizedPublicKey: %v", err)
|
|
}
|
|
parsed, _, _, _, err := ssh.ParseAuthorizedKey(publicKey)
|
|
if err != nil {
|
|
t.Fatalf("ParseAuthorizedKey: %v", err)
|
|
}
|
|
if parsed.Type() != ssh.KeyAlgoRSA {
|
|
t.Fatalf("key type = %q, want %q", parsed.Type(), ssh.KeyAlgoRSA)
|
|
}
|
|
}
|
|
|
|
func TestWriteTarEntriesArchiveIncludesOnlySelectedPaths(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
sourceDir := filepath.Join(t.TempDir(), "repo")
|
|
if err := os.MkdirAll(filepath.Join(sourceDir, "nested"), 0o755); err != nil {
|
|
t.Fatalf("MkdirAll(nested): %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "tracked.txt"), []byte("tracked"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile(tracked.txt): %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "nested", "keep.txt"), []byte("keep"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile(keep.txt): %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "nested", "skip.txt"), []byte("skip"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile(skip.txt): %v", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := writeTarEntriesArchive(&buf, sourceDir, []string{"tracked.txt", "nested/keep.txt"}); err != nil {
|
|
t.Fatalf("writeTarEntriesArchive: %v", err)
|
|
}
|
|
|
|
tr := tar.NewReader(bytes.NewReader(buf.Bytes()))
|
|
var names []string
|
|
for {
|
|
header, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("tar.Next: %v", err)
|
|
}
|
|
names = append(names, header.Name)
|
|
}
|
|
|
|
want := map[string]struct{}{
|
|
"repo/tracked.txt": {},
|
|
"repo/nested/keep.txt": {},
|
|
}
|
|
if len(names) != len(want) {
|
|
t.Fatalf("archive names = %v, want %d entries", names, len(want))
|
|
}
|
|
for _, name := range names {
|
|
if _, ok := want[name]; !ok {
|
|
t.Fatalf("unexpected archive entry %q in %v", name, names)
|
|
}
|
|
}
|
|
}
|