package nodesync import ( "os" "os/exec" "os/user" "path/filepath" "strings" "testing" ) // TestPrepareScriptForReal runs the prepare script and restore.sh with the // local shell in a temporary folder (every path prefixed with it). func TestPrepareScriptForReal(t *testing.T) { if _, err := exec.LookPath("sha256sum"); err != nil { t.Skip("sha256sum not installed") } root := t.TempDir() u, _ := user.Current() g, _ := user.LookupGroupId(u.Gid) owner := u.Username + ":" + g.Name write := func(p, s string) { if err := os.MkdirAll(filepath.Dir(root+p), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(root+p, []byte(s), 0o644); err != nil { t.Fatal(err) } } read := func(p string) string { b, err := os.ReadFile(root + p) if err != nil { return "" } return string(b) } write("/etc/sysctl.d/90-net.conf", "old\n") write("/etc/sysctl.d/99-local.conf", "local\n") write("/etc/network/interfaces", "auto lo\n") plan := Plan{Changes: []Change{ {Item: "sysctl", ID: "/etc/sysctl.d/90-net.conf", Op: OpUpdate, Hash: hash([]byte("old\n")), Mode: "640", Owner: owner, Content: []byte("new\n")}, {Item: "multipath", ID: "/etc/multipath.conf", Op: OpCreate, Hash: "absent", Mode: "644", Owner: owner, Content: []byte("defaults {}\n")}, {Item: "sysctl", ID: "/etc/sysctl.d/99-local.conf", Op: OpDelete, Hash: hash([]byte("local\n"))}, {Item: "network", ID: "vmbr0", Op: OpUpdate}, }} run := func(script string, in []byte) (string, error) { cmd := exec.Command("sh", "-c", script) cmd.Stdin = strings.NewReader(string(in)) out, err := cmd.CombinedOutput() return string(out), err } const dir = "/var/lib/pve-confsync/backup/test" if out, err := run(prepareScriptIn(dir, root), prepareInput(plan, true, false)); err != nil || strings.TrimSpace(out) != "ok" { t.Fatalf("prepare: %v %s", err, out) } if read("/etc/sysctl.d/90-net.conf") != "new\n" || read("/etc/multipath.conf") != "defaults {}\n" || read("/etc/sysctl.d/99-local.conf") != "" { t.Fatalf("writes: %q %q %q", read("/etc/sysctl.d/90-net.conf"), read("/etc/multipath.conf"), read("/etc/sysctl.d/99-local.conf")) } if fi, _ := os.Stat(root + "/etc/sysctl.d/90-net.conf"); fi.Mode().Perm() != 0o640 { t.Errorf("mode %v", fi.Mode()) } if m, _ := filepath.Glob(root + "/etc/sysctl.d/*.confsync.*"); len(m) > 0 { t.Errorf("temporary files left: %v", m) } // a second run with the same plan: the files changed, nothing is written if out, err := run(prepareScriptIn(dir+"2", root), prepareInput(plan, false, false)); err == nil || !strings.Contains(out, "changed since the dry-run") { t.Errorf("stale check: %v %s", err, out) } write("/etc/network/interfaces.new", "pending\n") if out, err := run("sh '"+root+dir+"/restore.sh'", nil); err != nil { t.Fatalf("restore: %v %s", err, out) } if read("/etc/sysctl.d/90-net.conf") != "old\n" || read("/etc/multipath.conf") != "" || read("/etc/sysctl.d/99-local.conf") != "local\n" || read("/etc/network/interfaces") != "auto lo\n" || read("/etc/network/interfaces.new") != "" { t.Errorf("restore: %q %q %q %q", read("/etc/sysctl.d/90-net.conf"), read("/etc/multipath.conf"), read("/etc/sysctl.d/99-local.conf"), read("/etc/network/interfaces.new")) } }