The DNS role installs ddi-suite-keys.conf (a keys file without keys for revisions from before M5, so that named.conf can always include it), includes it before ddi-suite.conf in the validation named.conf, accepts secondary zone files in <zone_dir>/secondary/ and creates that directory with the mode and owner of zone_dir without ever touching its content. The health check queries secondary zones too; a zone still waiting for its first transfer is reported but does not fail the deployment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
176 lines
4.7 KiB
Go
176 lines
4.7 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package fsutil
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestWriteFileReplacesAtomically(t *testing.T) {
|
|
dir := t.TempDir()
|
|
p := filepath.Join(dir, "f.conf")
|
|
if err := os.WriteFile(p, []byte("old"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := WriteFile(p, []byte("new"), 0o640, OwnerFor(p)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := os.ReadFile(p)
|
|
if err != nil || string(got) != "new" {
|
|
t.Fatalf("content = %q, %v", got, err)
|
|
}
|
|
if runtime.GOOS != "windows" {
|
|
fi, _ := os.Stat(p)
|
|
if fi.Mode().Perm() != 0o640 {
|
|
t.Errorf("mode = %o, want 640", fi.Mode().Perm())
|
|
}
|
|
}
|
|
entries, _ := os.ReadDir(dir)
|
|
for _, e := range entries {
|
|
if strings.HasPrefix(e.Name(), tempPrefix) {
|
|
t.Errorf("temporary file left behind: %s", e.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWriteFileFailsWithoutDirectory(t *testing.T) {
|
|
if err := WriteFile(filepath.Join(t.TempDir(), "missing", "f"), []byte("x"), 0o640, Owner{}); err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
}
|
|
|
|
func TestBackupRestore(t *testing.T) {
|
|
dir := t.TempDir()
|
|
existing := filepath.Join(dir, "existing")
|
|
created := filepath.Join(dir, "created")
|
|
removed := filepath.Join(dir, "removed")
|
|
for _, p := range []string{existing, removed} {
|
|
if err := os.WriteFile(p, []byte("orig "+filepath.Base(p)), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
var b Backup
|
|
for _, p := range []string{existing, created, removed, existing} {
|
|
if err := b.Save(p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if got := len(b.Paths()); got != 3 {
|
|
t.Fatalf("saved %d paths, want 3 (duplicates ignored)", got)
|
|
}
|
|
// Change everything.
|
|
if err := WriteFile(existing, []byte("changed"), 0o600, Owner{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := WriteFile(created, []byte("new"), 0o600, Owner{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := Remove(removed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := b.Restore(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for p, want := range map[string]string{existing: "orig existing", removed: "orig removed"} {
|
|
got, err := os.ReadFile(p)
|
|
if err != nil || string(got) != want {
|
|
t.Errorf("%s = %q, %v; want %q", p, got, err, want)
|
|
}
|
|
if runtime.GOOS != "windows" {
|
|
if fi, _ := os.Stat(p); fi.Mode().Perm() != 0o640 {
|
|
t.Errorf("%s: mode %o not restored", p, fi.Mode().Perm())
|
|
}
|
|
}
|
|
}
|
|
if ok, _ := Exists(created); ok {
|
|
t.Error("file created after the backup was not removed")
|
|
}
|
|
}
|
|
|
|
func TestBackupRefusesDirectories(t *testing.T) {
|
|
var b Backup
|
|
if err := b.Save(t.TempDir()); err == nil {
|
|
t.Fatal("expected an error for a directory")
|
|
}
|
|
}
|
|
|
|
func TestSame(t *testing.T) {
|
|
p := filepath.Join(t.TempDir(), "f")
|
|
if Same(p, []byte("x"), 0o640) {
|
|
t.Error("missing file reported as same")
|
|
}
|
|
if err := WriteFile(p, []byte("x"), 0o640, Owner{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !Same(p, []byte("x"), 0o640) && runtime.GOOS != "windows" {
|
|
t.Error("identical file not recognized")
|
|
}
|
|
if Same(p, []byte("y"), 0o640) {
|
|
t.Error("different content reported as same")
|
|
}
|
|
if runtime.GOOS != "windows" && Same(p, []byte("x"), 0o600) {
|
|
t.Error("different mode reported as same")
|
|
}
|
|
}
|
|
|
|
func TestMkdirLike(t *testing.T) {
|
|
ref := filepath.Join(t.TempDir(), "zones")
|
|
if err := os.Mkdir(ref, 0o750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Chmod(ref, 0o770); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sub := filepath.Join(ref, "secondary")
|
|
if err := MkdirLike(sub, ref); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fi, err := os.Stat(sub)
|
|
if err != nil || !fi.IsDir() {
|
|
t.Fatalf("not created: %v", err)
|
|
}
|
|
if runtime.GOOS != "windows" && fi.Mode().Perm() != 0o770 {
|
|
t.Errorf("mode = %o, want 770", fi.Mode().Perm())
|
|
}
|
|
// Existing directories stay as they are (content included).
|
|
if err := os.WriteFile(filepath.Join(sub, "db.x"), []byte("x"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := MkdirLike(sub, ref); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(sub, "db.x")); err != nil {
|
|
t.Error("content of the existing directory changed")
|
|
}
|
|
// A file in the way and a missing reference are errors.
|
|
file := filepath.Join(ref, "file")
|
|
if err := os.WriteFile(file, nil, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if MkdirLike(file, ref) == nil {
|
|
t.Error("file accepted as directory")
|
|
}
|
|
if MkdirLike(filepath.Join(ref, "other"), filepath.Join(ref, "missing")) == nil {
|
|
t.Error("missing reference accepted")
|
|
}
|
|
if MkdirLike(filepath.Join(ref, "other"), file) == nil {
|
|
t.Error("file accepted as reference")
|
|
}
|
|
}
|
|
|
|
func TestRequireDir(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := RequireDir(dir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f := filepath.Join(dir, "f")
|
|
_ = os.WriteFile(f, nil, 0o600)
|
|
if RequireDir(f) == nil || RequireDir(filepath.Join(dir, "nope")) == nil {
|
|
t.Fatal("expected errors")
|
|
}
|
|
}
|