Sync Git identity into guest VMs
Populate guest /root/.gitconfig from host git config --global during work-disk preparation so plain VM shells can commit. Resolve user.name and user.email from the source repo for vm run and write them only into the imported checkout, preserving repo-specific identity overrides. Update mounted guest .gitconfig through a host temp file plus sudo install instead of direct git config --file writes, since the mounted root-owned work disk blocks Git lockfile creation. Validated with GOCACHE=/tmp/banger-gocache go test ./..., make build, and a live alpine vm create smoke check for guest git config.
This commit is contained in:
parent
f798e1db33
commit
42b4a18c63
5 changed files with 308 additions and 0 deletions
|
|
@ -808,6 +808,121 @@ func TestEnsureAuthorizedKeyOnWorkDiskRepairsNestedRootLayout(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEnsureGitIdentityOnWorkDiskCopiesHostGlobalIdentity(t *testing.T) {
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
t.Skip("git not installed")
|
||||
}
|
||||
|
||||
hostConfigPath := filepath.Join(t.TempDir(), "host.gitconfig")
|
||||
t.Setenv("GIT_CONFIG_GLOBAL", hostConfigPath)
|
||||
testSetGitConfig(t, "user.name", "Banger Host")
|
||||
testSetGitConfig(t, "user.email", "host@example.com")
|
||||
|
||||
workDiskDir := t.TempDir()
|
||||
d := &Daemon{runner: &filesystemRunner{t: t}}
|
||||
vm := testVM("git-identity", "image-git-identity", "172.16.0.67")
|
||||
vm.Runtime.WorkDiskPath = workDiskDir
|
||||
|
||||
if err := d.ensureGitIdentityOnWorkDisk(context.Background(), &vm); err != nil {
|
||||
t.Fatalf("ensureGitIdentityOnWorkDisk: %v", err)
|
||||
}
|
||||
|
||||
guestConfigPath := filepath.Join(workDiskDir, workDiskGitConfigRelativePath)
|
||||
if got := testGitConfigValue(t, guestConfigPath, "user.name"); got != "Banger Host" {
|
||||
t.Fatalf("guest user.name = %q, want Banger Host", got)
|
||||
}
|
||||
if got := testGitConfigValue(t, guestConfigPath, "user.email"); got != "host@example.com" {
|
||||
t.Fatalf("guest user.email = %q, want host@example.com", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureGitIdentityOnWorkDiskPreservesExistingGuestConfig(t *testing.T) {
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
t.Skip("git not installed")
|
||||
}
|
||||
|
||||
hostConfigPath := filepath.Join(t.TempDir(), "host.gitconfig")
|
||||
t.Setenv("GIT_CONFIG_GLOBAL", hostConfigPath)
|
||||
testSetGitConfig(t, "user.name", "Fresh Name")
|
||||
testSetGitConfig(t, "user.email", "fresh@example.com")
|
||||
|
||||
workDiskDir := t.TempDir()
|
||||
guestConfigPath := filepath.Join(workDiskDir, workDiskGitConfigRelativePath)
|
||||
if err := os.WriteFile(guestConfigPath, []byte("[safe]\n\tdirectory = /root/repo\n[user]\n\tname = stale\n"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile(guest .gitconfig): %v", err)
|
||||
}
|
||||
|
||||
d := &Daemon{runner: &filesystemRunner{t: t}}
|
||||
vm := testVM("git-identity-preserve", "image-git-identity", "172.16.0.68")
|
||||
vm.Runtime.WorkDiskPath = workDiskDir
|
||||
|
||||
if err := d.ensureGitIdentityOnWorkDisk(context.Background(), &vm); err != nil {
|
||||
t.Fatalf("ensureGitIdentityOnWorkDisk: %v", err)
|
||||
}
|
||||
|
||||
if got := testGitConfigValue(t, guestConfigPath, "user.name"); got != "Fresh Name" {
|
||||
t.Fatalf("guest user.name = %q, want Fresh Name", got)
|
||||
}
|
||||
if got := testGitConfigValue(t, guestConfigPath, "user.email"); got != "fresh@example.com" {
|
||||
t.Fatalf("guest user.email = %q, want fresh@example.com", got)
|
||||
}
|
||||
if got := testGitConfigValue(t, guestConfigPath, "safe.directory"); got != "/root/repo" {
|
||||
t.Fatalf("guest safe.directory = %q, want /root/repo", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureGitIdentityOnWorkDiskWarnsAndSkipsWhenHostIdentityIncomplete(t *testing.T) {
|
||||
if _, err := exec.LookPath("git"); err != nil {
|
||||
t.Skip("git not installed")
|
||||
}
|
||||
|
||||
hostConfigPath := filepath.Join(t.TempDir(), "host.gitconfig")
|
||||
t.Setenv("GIT_CONFIG_GLOBAL", hostConfigPath)
|
||||
testSetGitConfig(t, "user.name", "Only Name")
|
||||
|
||||
workDiskDir := t.TempDir()
|
||||
guestConfigPath := filepath.Join(workDiskDir, workDiskGitConfigRelativePath)
|
||||
original := []byte("[user]\n\temail = keep@example.com\n")
|
||||
if err := os.WriteFile(guestConfigPath, original, 0o644); err != nil {
|
||||
t.Fatalf("WriteFile(guest .gitconfig): %v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
logger, _, err := newDaemonLogger(&buf, "info")
|
||||
if err != nil {
|
||||
t.Fatalf("newDaemonLogger: %v", err)
|
||||
}
|
||||
|
||||
d := &Daemon{
|
||||
runner: &filesystemRunner{t: t},
|
||||
logger: logger,
|
||||
}
|
||||
vm := testVM("git-identity-missing", "image-git-identity", "172.16.0.69")
|
||||
vm.Runtime.WorkDiskPath = workDiskDir
|
||||
|
||||
if err := d.ensureGitIdentityOnWorkDisk(context.Background(), &vm); err != nil {
|
||||
t.Fatalf("ensureGitIdentityOnWorkDisk: %v", err)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(guestConfigPath)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile(guest .gitconfig): %v", err)
|
||||
}
|
||||
if string(got) != string(original) {
|
||||
t.Fatalf("guest .gitconfig = %q, want preserved %q", string(got), string(original))
|
||||
}
|
||||
|
||||
entries := parseLogEntries(t, buf.Bytes())
|
||||
if !hasLogEntry(entries, map[string]string{
|
||||
"msg": "guest git identity sync skipped",
|
||||
"vm_name": vm.Name,
|
||||
"source": hostGlobalGitIdentitySource,
|
||||
"error": "host git user.email is empty",
|
||||
}) {
|
||||
t.Fatalf("expected warn log, got %v", entries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureOpencodeAuthOnWorkDiskCopiesHostAuth(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
t.Setenv("HOME", homeDir)
|
||||
|
|
@ -1599,6 +1714,27 @@ func startHTTPSServerOnTCP4(t *testing.T, handler http.Handler) *net.TCPAddr {
|
|||
return listener.Addr().(*net.TCPAddr)
|
||||
}
|
||||
|
||||
func testSetGitConfig(t *testing.T, key, value string) {
|
||||
t.Helper()
|
||||
|
||||
cmd := exec.Command("git", "config", "--global", key, value)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("git config --global %s: %v: %s", key, err, strings.TrimSpace(string(output)))
|
||||
}
|
||||
}
|
||||
|
||||
func testGitConfigValue(t *testing.T, configPath, key string) string {
|
||||
t.Helper()
|
||||
|
||||
cmd := exec.Command("git", "config", "--file", configPath, "--get", key)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("git config --file %s --get %s: %v: %s", configPath, key, err, strings.TrimSpace(string(output)))
|
||||
}
|
||||
return strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
type processKillingRunner struct {
|
||||
*scriptedRunner
|
||||
proc *exec.Cmd
|
||||
|
|
@ -1610,6 +1746,20 @@ type filesystemRunner struct {
|
|||
|
||||
func (r *filesystemRunner) Run(ctx context.Context, name string, args ...string) ([]byte, error) {
|
||||
r.t.Helper()
|
||||
if name == "git" {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
if stderr.Len() > 0 {
|
||||
return stdout.Bytes(), fmt.Errorf("%w: %s", err, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
return stdout.Bytes(), err
|
||||
}
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected Run call: %s %v", name, args)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue