149 lines
5.5 KiB
Go
149 lines
5.5 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// configMapBody extracts the `config.yaml: |` block from a k8s manifest. The
|
|
// ConfigMap is the thing an operator actually applies, and nothing in the test
|
|
// suite loads it, so a typo in it is invisible until a pod crash-loops.
|
|
func configMapBody(t *testing.T, manifest, key string) string {
|
|
t.Helper()
|
|
raw, err := os.ReadFile(manifest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
lines := strings.Split(string(raw), "\n")
|
|
header := " " + key + ": |"
|
|
start := -1
|
|
for i, line := range lines {
|
|
if strings.TrimRight(line, " \r") == header {
|
|
start = i + 1
|
|
break
|
|
}
|
|
}
|
|
if start < 0 {
|
|
t.Fatalf("%s has no %q block", manifest, key)
|
|
}
|
|
var body []string
|
|
for _, line := range lines[start:] {
|
|
// The block scalar's content is indented past the key; the first
|
|
// line that is not ends the block.
|
|
if strings.TrimSpace(line) != "" && !strings.HasPrefix(line, " ") {
|
|
break
|
|
}
|
|
body = append(body, strings.TrimPrefix(line, " "))
|
|
}
|
|
if len(body) == 0 {
|
|
t.Fatalf("%s: the %q block is empty", manifest, key)
|
|
}
|
|
return strings.Join(body, "\n")
|
|
}
|
|
|
|
// writeTemp writes body to a temp config file and returns its path. Load
|
|
// resolves relative paths against the file's directory, so the fixture has to
|
|
// be a real file in a real directory rather than an in-memory string.
|
|
func writeTemp(t *testing.T, body string) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "config.yaml")
|
|
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
// TestExampleConfigsValidate loads the two configuration files we ship and
|
|
// requires both to survive Load, which ends in Validate. Everything else in
|
|
// this file is free to be wrong; a shipped example that cannot start is not.
|
|
func TestExampleConfigsValidate(t *testing.T) {
|
|
t.Run("deploy/config.example.yaml", func(t *testing.T) {
|
|
cfg, err := Load(filepath.Join("..", "..", "deploy", "config.example.yaml"))
|
|
if err != nil {
|
|
t.Fatalf("the shipped example config does not load: %v", err)
|
|
}
|
|
// Spot-check that the example is not merely valid but is saying what
|
|
// its comments say: the API is open for writes here, metrics are on,
|
|
// and the three blocklist sources are the three the file lists.
|
|
if !cfg.API.AllowConfigWrite {
|
|
t.Error("config.example.yaml documents config writes but sets allow_config_write: false")
|
|
}
|
|
if !cfg.Metrics.Enabled || !cfg.Metrics.Protect {
|
|
t.Error("config.example.yaml should ship metrics enabled and protected")
|
|
}
|
|
if got := len(cfg.Block.Lists); got != 3 {
|
|
t.Errorf("blocklist lists = %d, want 3 (haas, easylist, local-rpz)", got)
|
|
}
|
|
if got := cfg.Memory.Reserve; got != 256*1024*1024 {
|
|
t.Errorf("memory.reserve = %d, want 256MiB as documented", int64(got))
|
|
}
|
|
// The example must not carry a key that looks like a real one. This
|
|
// is the file people copy, and a working key in it is a published
|
|
// credential.
|
|
for _, k := range cfg.API.Auth.APIKeys {
|
|
if len(k) > 12 && !strings.Contains(k, "CHANGE-ME") {
|
|
t.Errorf("config.example.yaml ships an API key that is not a "+
|
|
"placeholder: %q", k)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("deploy/k8s/00-config.yaml ConfigMap", func(t *testing.T) {
|
|
manifest := filepath.Join("..", "..", "deploy", "k8s", "00-config.yaml")
|
|
body := configMapBody(t, manifest, "config.yaml")
|
|
// The key comes from a Secret, so the manifest's ${FANCYDNS_API_KEY}
|
|
// has to be set for the config to load at all — which is the point:
|
|
// a missing Secret is a crash loop, not an open console.
|
|
t.Setenv("FANCYDNS_API_KEY", "k8s-manifest-fixture-key")
|
|
|
|
cfg, err := Load(writeTemp(t, body))
|
|
if err != nil {
|
|
t.Fatalf("the k8s ConfigMap does not load: %v", err)
|
|
}
|
|
if got := cfg.API.Auth.APIKeys; len(got) != 1 || got[0] != "k8s-manifest-fixture-key" {
|
|
t.Errorf("api keys = %q, want the expanded ${FANCYDNS_API_KEY}", got)
|
|
}
|
|
if cfg.API.Auth.TrustLoopback {
|
|
t.Error("the k8s manifest exposes the API on 0.0.0.0, so trust_loopback must be false")
|
|
}
|
|
if cfg.API.AllowConfigWrite {
|
|
t.Error("the k8s manifest sets allow_config_write: false; a ConfigMap " +
|
|
"is not writable from the pod")
|
|
}
|
|
// The memory envelope is matched to the StatefulSet's 1Gi limit, and a
|
|
// limit above the cgroup's is the cgroup kill AGENTS.md warns about.
|
|
if got := int64(cfg.Memory.Limit); got != 1024*1024*1024 {
|
|
t.Errorf("memory.limit = %d, want 1GiB", got)
|
|
}
|
|
if got := int64(cfg.Memory.CacheMaxBytes); got != 384*1024*1024 {
|
|
t.Errorf("memory.cache_max_bytes = %d, want 384MiB", got)
|
|
}
|
|
if got := cfg.Cache.Size; got != 500000 {
|
|
t.Errorf("cache.size = %d, want the 500000 the manifest documents", got)
|
|
}
|
|
if !cfg.DNS.DoT.Enabled || !cfg.DNS.DoH.Enabled {
|
|
t.Error("the k8s manifest enables both DoT and DoH; both are required for the Ingress")
|
|
}
|
|
if !cfg.Metrics.Protect {
|
|
t.Error("metrics.protect must stay true in the cluster")
|
|
}
|
|
if got := cfg.Log.Format; got != "json" {
|
|
t.Errorf("log.format = %q, want json in a cluster", got)
|
|
}
|
|
})
|
|
|
|
t.Run("an unset API key variable is still fatal", func(t *testing.T) {
|
|
// The control for the case above: the expansion does not fall back to
|
|
// something harmless when the Secret is missing.
|
|
manifest := filepath.Join("..", "..", "deploy", "k8s", "00-config.yaml")
|
|
t.Setenv("FANCYDNS_API_KEY", "")
|
|
os.Unsetenv("FANCYDNS_API_KEY")
|
|
if _, err := Load(writeTemp(t, configMapBody(t, manifest, "config.yaml"))); err == nil {
|
|
t.Fatal("the k8s ConfigMap loaded with no API key in the environment; " +
|
|
"a missing Secret must be a startup failure")
|
|
}
|
|
})
|
|
}
|