The console now works as the cluster's editor, without the monitoring pages. Web: Blocklists gets Add/Edit/Remove for BlockList objects (and shows ones the resolver has not compiled yet); Clients marks cluster groups and locks config-file groups and the default policy; Upstreams becomes a read-only health view when the config cannot be written; Zones and Domains lose the managed-directory and list copy; the Local zones page and its API calls are removed; nav is Status, Blocklists, Domains, Lookup, Address redirects, Zones, Clients, Upstreams, Settings. Console: a change to a group the resolver's config file defines is refused with the reason instead of being skipped silently, and /api/clients says what the console can and cannot edit. Server: isFileWritable called a read-only existing file (a mounted ConfigMap) writable, and api.allow_config_write was not reflected in the writable flag nor checked by the upstream/ACL edit path. Both fixed; tests pin them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
52 lines
1.5 KiB
Go
52 lines
1.5 KiB
Go
package api
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
// A config file that exists but refuses a write is not writable. This is what a
|
|
// ConfigMap mounted into a pod looks like, and calling it writable made the
|
|
// console offer an editor whose every save failed.
|
|
func TestIsFileWritable(t *testing.T) {
|
|
if os.Geteuid() == 0 {
|
|
t.Skip("root can write a read-only file")
|
|
}
|
|
dir := t.TempDir()
|
|
|
|
rw := filepath.Join(dir, "rw.yaml")
|
|
if err := os.WriteFile(rw, []byte("x"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ro := filepath.Join(dir, "ro.yaml")
|
|
if err := os.WriteFile(ro, []byte("x"), 0o444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cases := map[string]struct {
|
|
path string
|
|
want bool
|
|
}{
|
|
"writable file": {rw, true},
|
|
"read-only file": {ro, false},
|
|
"missing file, open dir": {filepath.Join(dir, "new.yaml"), true},
|
|
"missing file, no dir": {filepath.Join(dir, "nodir", "x.yaml"), false},
|
|
"no path": {"", false},
|
|
}
|
|
for name, tc := range cases {
|
|
if got := isFileWritable(tc.path); got != tc.want {
|
|
t.Errorf("%s: isFileWritable(%q) = %v, want %v", name, tc.path, got, tc.want)
|
|
}
|
|
}
|
|
|
|
// A directory that refuses new files makes a missing file unwritable too.
|
|
locked := filepath.Join(dir, "locked")
|
|
if err := os.Mkdir(locked, 0o555); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = os.Chmod(locked, 0o755) })
|
|
if isFileWritable(filepath.Join(locked, "x.yaml")) {
|
|
t.Error("a file in a directory that refuses new files was reported writable")
|
|
}
|
|
}
|