mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-20 21:23:07 +08:00
ipn/conffile: add ErrNoConfig for absent config sources
Load wraps read-phase failures with it so callers can distinguish a missing config from an invalid one. Updates #1866 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
318807bdb9
commit
d9aeaa504b
@ -8,6 +8,7 @@
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
@ -40,6 +41,15 @@ func (c *Config) WantRunning() bool {
|
||||
// from the VM's metadata service's user-data field.
|
||||
const VMUserDataPath = "vm:user-data"
|
||||
|
||||
// ErrNoConfig is returned (wrapped) by Load when the config source is absent or
|
||||
// unreadable: a missing file, an EC2 instance with no user-data, an unreachable
|
||||
// metadata service, or a build without the relevant cloud support. It reports
|
||||
// "no config was provided", as distinct from "a config was provided but is
|
||||
// invalid" (which Load returns as an unwrapped parse/validate error). Callers
|
||||
// that want to boot unconfigured when no config is present (e.g. tailscaled's
|
||||
// "optional:" -config prefix) can check for it with errors.Is.
|
||||
var ErrNoConfig = errors.New("no config present")
|
||||
|
||||
// hujsonStandardize is set to hujson.Standardize by conffile_hujson.go on
|
||||
// platforms that support config files.
|
||||
var hujsonStandardize func([]byte) ([]byte, error)
|
||||
@ -62,7 +72,10 @@ func Load(path string) (*Config, error) {
|
||||
c.Raw, err = os.ReadFile(path)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Read-phase failure: the config source is absent or unreadable.
|
||||
// Wrap ErrNoConfig so callers can distinguish this from a config
|
||||
// that was read but failed to parse below.
|
||||
return nil, fmt.Errorf("%w: %v", ErrNoConfig, err)
|
||||
}
|
||||
if buildfeatures.HasHuJSONConf && hujsonStandardize != nil {
|
||||
c.Std, err = hujsonStandardize(c.Raw)
|
||||
|
||||
60
ipn/conffile/conffile_test.go
Normal file
60
ipn/conffile/conffile_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package conffile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestLoadAbsentIsErrNoConfig verifies that an absent config source (here a
|
||||
// missing file, the same read-phase seam the "vm:user-data" 404 goes through)
|
||||
// is reported as ErrNoConfig, so callers using tailscaled's "optional:" prefix
|
||||
// can boot unconfigured instead of failing.
|
||||
func TestLoadAbsentIsErrNoConfig(t *testing.T) {
|
||||
_, err := Load(filepath.Join(t.TempDir(), "does-not-exist.json"))
|
||||
if !errors.Is(err, ErrNoConfig) {
|
||||
t.Fatalf("Load(missing) error = %v; want it to wrap ErrNoConfig", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoadInvalidIsNotErrNoConfig verifies that a config that is present but
|
||||
// invalid is NOT ErrNoConfig, so it stays fatal even in optional mode.
|
||||
func TestLoadInvalidIsNotErrNoConfig(t *testing.T) {
|
||||
for name, data := range map[string]string{
|
||||
"malformed": `{"Version": "alpha0"`, // truncated JSON
|
||||
"unknown-version": `{"Version": "bogus9"}`,
|
||||
"unknown-field": `{"Version": "alpha0", "Bogus": true}`,
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
p := filepath.Join(t.TempDir(), "bad.json")
|
||||
if err := os.WriteFile(p, []byte(data), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err := Load(p)
|
||||
if err == nil {
|
||||
t.Fatalf("Load(%q) succeeded; want an error", data)
|
||||
}
|
||||
if errors.Is(err, ErrNoConfig) {
|
||||
t.Fatalf("Load(%q) error = %v; want a parse error, not ErrNoConfig", data, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadValid(t *testing.T) {
|
||||
p := filepath.Join(t.TempDir(), "ok.json")
|
||||
if err := os.WriteFile(p, []byte(`{"Version": "alpha0"}`), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := Load(p)
|
||||
if err != nil {
|
||||
t.Fatalf("Load(valid) error = %v; want nil", err)
|
||||
}
|
||||
if c.Version != "alpha0" {
|
||||
t.Fatalf("Version = %q; want alpha0", c.Version)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user