cmd/tailscale/cli: return error on duplicate multi-value flags (#15534)

Some CLI flags support multiple values separated by commas. These flags
are intended to be declared only once and will silently ignore subsequent
instances. This will now throw an error if multiple instances of advertise-tags
and advertise-routes are detected.

Fixes #6813

Signed-off-by: Jason O'Donnell <2160810+jasonodonnell@users.noreply.github.com>
This commit is contained in:
Jason O'Donnell
2025-04-08 14:12:17 -04:00
committed by GitHub
parent 025fe72448
commit 6088ee311f
4 changed files with 100 additions and 20 deletions
+68 -7
View File
@@ -657,6 +657,13 @@ func upArgsFromOSArgs(goos string, flagArgs ...string) (args upArgsT) {
return
}
func newSingleUseStringForTest(v string) singleUseStringFlag {
return singleUseStringFlag{
set: true,
value: v,
}
}
func TestPrefsFromUpArgs(t *testing.T) {
tests := []struct {
name string
@@ -721,14 +728,14 @@ func TestPrefsFromUpArgs(t *testing.T) {
{
name: "error_advertise_route_invalid_ip",
args: upArgsT{
advertiseRoutes: "foo",
advertiseRoutes: newSingleUseStringForTest("foo"),
},
wantErr: `"foo" is not a valid IP address or CIDR prefix`,
},
{
name: "error_advertise_route_unmasked_bits",
args: upArgsT{
advertiseRoutes: "1.2.3.4/16",
advertiseRoutes: newSingleUseStringForTest("1.2.3.4/16"),
},
wantErr: `1.2.3.4/16 has non-address bits set; expected 1.2.0.0/16`,
},
@@ -749,7 +756,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
{
name: "error_tag_prefix",
args: upArgsT{
advertiseTags: "foo",
advertiseTags: newSingleUseStringForTest("foo"),
},
wantErr: `tag: "foo": tags must start with 'tag:'`,
},
@@ -829,7 +836,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
name: "via_route_good",
goos: "linux",
args: upArgsT{
advertiseRoutes: "fd7a:115c:a1e0:b1a::bb:10.0.0.0/112",
advertiseRoutes: newSingleUseStringForTest("fd7a:115c:a1e0:b1a::bb:10.0.0.0/112"),
netfilterMode: "off",
},
want: &ipn.Prefs{
@@ -848,7 +855,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
name: "via_route_good_16_bit",
goos: "linux",
args: upArgsT{
advertiseRoutes: "fd7a:115c:a1e0:b1a::aabb:10.0.0.0/112",
advertiseRoutes: newSingleUseStringForTest("fd7a:115c:a1e0:b1a::aabb:10.0.0.0/112"),
netfilterMode: "off",
},
want: &ipn.Prefs{
@@ -867,7 +874,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
name: "via_route_short_prefix",
goos: "linux",
args: upArgsT{
advertiseRoutes: "fd7a:115c:a1e0:b1a::/64",
advertiseRoutes: newSingleUseStringForTest("fd7a:115c:a1e0:b1a::/64"),
netfilterMode: "off",
},
wantErr: "fd7a:115c:a1e0:b1a::/64 4-in-6 prefix must be at least a /96",
@@ -876,7 +883,7 @@ func TestPrefsFromUpArgs(t *testing.T) {
name: "via_route_short_reserved_siteid",
goos: "linux",
args: upArgsT{
advertiseRoutes: "fd7a:115c:a1e0:b1a:1234:5678::/112",
advertiseRoutes: newSingleUseStringForTest("fd7a:115c:a1e0:b1a:1234:5678::/112"),
netfilterMode: "off",
},
wantErr: "route fd7a:115c:a1e0:b1a:1234:5678::/112 contains invalid site ID 12345678; must be 0xffff or less",
@@ -1106,6 +1113,7 @@ func TestUpdatePrefs(t *testing.T) {
},
env: upCheckEnv{backendState: "Running"},
},
{
// Issue 3808: explicitly empty --operator= should clear value.
name: "explicit_empty_operator",
@@ -1598,3 +1606,56 @@ func TestDepsNoCapture(t *testing.T) {
}.Check(t)
}
func TestSingleUseStringFlag(t *testing.T) {
tests := []struct {
name string
setValues []string
wantValue string
wantErr bool
}{
{
name: "set once",
setValues: []string{"foo"},
wantValue: "foo",
wantErr: false,
},
{
name: "set twice",
setValues: []string{"foo", "bar"},
wantValue: "foo",
wantErr: true,
},
{
name: "set nothing",
setValues: []string{},
wantValue: "",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var flag singleUseStringFlag
var lastErr error
for _, val := range tt.setValues {
lastErr = flag.Set(val)
}
if tt.wantErr {
if lastErr == nil {
t.Errorf("expected error on final Set, got nil")
}
} else {
if lastErr != nil {
t.Errorf("unexpected error on final Set: %v", lastErr)
}
}
if got := flag.String(); got != tt.wantValue {
t.Errorf("String() = %q, want %q", got, tt.wantValue)
}
})
}
}