diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3439d4182..8e583210f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -31,6 +31,7 @@ The sandbox cluster installation is running under the constant load generated by * SECURITY: upgrade Go builder from Go1.21.6 to Go1.21.7. See [the list of issues addressed in Go1.21.7](https://github.com/golang/go/issues?q=milestone%3AGo1.21.7+label%3ACherryPickApproved). * FEATURE: all VictoriaMetrics components: add support for TLS client certificate verification at `-httpListenAddr` (aka [mTLS](https://en.wikipedia.org/wiki/Mutual_authentication)). See [these docs](https://docs.victoriametrics.com/#mtls-protection). See [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5458). +* FEATURE: all VictoriaMetrics components: add support for empty command flag values in short array notation. For example, `-remoteWrite.sendTimeout=',20s,'` specifies three `-remoteWrite.sendTimeout` values - the first and the last ones are default values (`30s` in this case), while the second one is `20s`. * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html) and [single-node VictoriaMetrics](https://docs.victoriametrics.com): add support for data ingestion via [DataDog lambda extension](https://docs.datadoghq.com/serverless/libraries_integrations/extension/) aka `/api/beta/sketches` endpoint. See [these docs](https://docs.victoriametrics.com/#how-to-send-data-from-datadog-agent) and [this feature request](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3091). Thanks to @AndrewChubatiuk for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5584). * FEATURE: [VictoriaMetrics cluster](https://docs.victoriametrics.com/Cluster-VictoriaMetrics.html): add `-disableReroutingOnUnavailable` command-line flag, which can be used for reducing resource usage spikes at `vmstorage` nodes during rolling restart. Thanks to @Muxa1L for [the pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5713). * FEATURE: [dashboards/vmagent](https://grafana.com/grafana/dashboards/12683): add `Targets scraped/s` stat panel showing the number of targets scraped by the vmagent per-second. diff --git a/lib/flagutil/array.go b/lib/flagutil/array.go index 5ab4524a4..324596aa1 100644 --- a/lib/flagutil/array.go +++ b/lib/flagutil/array.go @@ -21,6 +21,7 @@ func NewArrayString(name, description string) *ArrayString { func NewArrayDuration(name string, defaultValue time.Duration, description string) *ArrayDuration { description += fmt.Sprintf(" (default %s)", defaultValue) description += "\nSupports `array` of values separated by comma or specified via multiple flags." + description += "\nEmpty values are set to default value." a := &ArrayDuration{ defaultValue: defaultValue, } @@ -31,6 +32,7 @@ func NewArrayDuration(name string, defaultValue time.Duration, description strin // NewArrayBool returns new ArrayBool with the given name and description. func NewArrayBool(name, description string) *ArrayBool { description += "\nSupports `array` of values separated by comma or specified via multiple flags." + description += "\nEmpty values are set to false." var a ArrayBool flag.Var(&a, name, description) return &a @@ -40,6 +42,7 @@ func NewArrayBool(name, description string) *ArrayBool { func NewArrayInt(name string, defaultValue int, description string) *ArrayInt { description += fmt.Sprintf(" (default %d)", defaultValue) description += "\nSupports `array` of values separated by comma or specified via multiple flags." + description += "\nEmpty values are set to default value." a := &ArrayInt{ defaultValue: defaultValue, } @@ -52,6 +55,7 @@ func NewArrayBytes(name string, defaultValue int64, description string) *ArrayBy description += "\nSupports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB." description += fmt.Sprintf(" (default %d)", defaultValue) description += "\nSupports `array` of values separated by comma or specified via multiple flags." + description += "\nEmpty values are set to default value." a := &ArrayBytes{ defaultValue: defaultValue, } @@ -241,6 +245,9 @@ func (a *ArrayBool) String() string { func (a *ArrayBool) Set(value string) error { values := parseArrayValues(value) for _, v := range values { + if v == "" { + v = "false" + } b, err := strconv.ParseBool(v) if err != nil { return err @@ -284,6 +291,9 @@ func (a *ArrayDuration) String() string { func (a *ArrayDuration) Set(value string) error { values := parseArrayValues(value) for _, v := range values { + if v == "" { + v = a.defaultValue.String() + } b, err := time.ParseDuration(v) if err != nil { return err @@ -332,6 +342,9 @@ func (a *ArrayInt) String() string { func (a *ArrayInt) Set(value string) error { values := parseArrayValues(value) for _, v := range values { + if v == "" { + v = fmt.Sprintf("%d", a.defaultValue) + } n, err := strconv.Atoi(v) if err != nil { return err @@ -376,6 +389,9 @@ func (a *ArrayBytes) Set(value string) error { values := parseArrayValues(value) for _, v := range values { var b Bytes + if v == "" { + v = fmt.Sprintf("%d", a.defaultValue) + } if err := b.Set(v); err != nil { return err } diff --git a/lib/flagutil/array_test.go b/lib/flagutil/array_test.go index ec4d87d76..6bb884fa6 100644 --- a/lib/flagutil/array_test.go +++ b/lib/flagutil/array_test.go @@ -165,6 +165,7 @@ func TestArrayDuration_Set(t *testing.T) { f := func(s, expectedResult string) { t.Helper() var a ArrayDuration + a.defaultValue = 42 * time.Second if err := a.Set(s); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -176,6 +177,7 @@ func TestArrayDuration_Set(t *testing.T) { f("", "") f(`1m`, `1m0s`) f(`5m,1s,1h`, `5m0s,1s,1h0m0s`) + f(`5m,,1h`, `5m0s,42s,1h0m0s`) } func TestArrayDuration_GetOptionalArg(t *testing.T) { @@ -238,6 +240,7 @@ func TestArrayBool_Set(t *testing.T) { f("", "") f(`true`, `true`) f(`false,True,False`, `false,true,false`) + f(`1,,False`, `true,false,false`) } func TestArrayBool_GetOptionalArg(t *testing.T) { @@ -289,6 +292,7 @@ func TestArrayInt_Set(t *testing.T) { f := func(s, expectedResult string, expectedValues []int) { t.Helper() var a ArrayInt + a.defaultValue = 42 if err := a.Set(s); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -304,6 +308,7 @@ func TestArrayInt_Set(t *testing.T) { f("", "", nil) f(`1`, `1`, []int{1}) f(`-2,3,-64`, `-2,3,-64`, []int{-2, 3, -64}) + f(`,,-64,`, `42,42,-64,42`, []int{42, 42, -64, 42}) } func TestArrayInt_GetOptionalArg(t *testing.T) { @@ -357,6 +362,7 @@ func TestArrayBytes_Set(t *testing.T) { f := func(s, expectedResult string) { t.Helper() var a ArrayBytes + a.defaultValue = 42 if err := a.Set(s); err != nil { t.Fatalf("unexpected error: %s", err) } @@ -368,6 +374,7 @@ func TestArrayBytes_Set(t *testing.T) { f("", "") f(`1`, `1`) f(`-2,3,10kb`, `-2,3,10KB`) + f(`,,10kb`, `42,42,10KB`) } func TestArrayBytes_GetOptionalArg(t *testing.T) { diff --git a/lib/flagutil/bytes.go b/lib/flagutil/bytes.go index 8b86c9c13..6f60a0aa0 100644 --- a/lib/flagutil/bytes.go +++ b/lib/flagutil/bytes.go @@ -47,6 +47,11 @@ func (b *Bytes) String() string { // Set implements flag.Value interface func (b *Bytes) Set(value string) error { + if value == "" { + b.N = 0 + b.valueString = "" + return nil + } value = normalizeBytesString(value) switch { case strings.HasSuffix(value, "KB"): diff --git a/lib/flagutil/bytes_test.go b/lib/flagutil/bytes_test.go index 4396b239b..584a9c8f4 100644 --- a/lib/flagutil/bytes_test.go +++ b/lib/flagutil/bytes_test.go @@ -12,7 +12,6 @@ func TestBytesSetFailure(t *testing.T) { t.Fatalf("expecting non-nil error in b.Set(%q)", value) } } - f("") f("foobar") f("5foobar") f("aKB") @@ -39,6 +38,7 @@ func TestBytesSetSuccess(t *testing.T) { t.Fatalf("unexpected valueString; got %q; want %q", valueString, valueExpected) } } + f("", 0) f("0", 0) f("1", 1) f("-1234", -1234) diff --git a/lib/flagutil/duration.go b/lib/flagutil/duration.go index 4af032331..8044e2708 100644 --- a/lib/flagutil/duration.go +++ b/lib/flagutil/duration.go @@ -49,6 +49,11 @@ func (d *Duration) String() string { // Set implements flag.Value interface func (d *Duration) Set(value string) error { + if value == "" { + d.msecs = 0 + d.valueString = "" + return nil + } // An attempt to parse value in months. months, err := strconv.ParseFloat(value, 64) if err == nil { diff --git a/lib/flagutil/duration_test.go b/lib/flagutil/duration_test.go index f9834c679..1e10dcfdc 100644 --- a/lib/flagutil/duration_test.go +++ b/lib/flagutil/duration_test.go @@ -14,7 +14,6 @@ func TestDurationSetFailure(t *testing.T) { t.Fatalf("expecting non-nil error in d.Set(%q)", value) } } - f("") f("foobar") f("5foobar") f("ah") @@ -51,6 +50,7 @@ func TestDurationSetSuccess(t *testing.T) { t.Fatalf("unexpected valueString; got %q; want %q", valueString, valueExpected) } } + f("", 0) f("0", 0) f("1", msecsPer31Days) f("123.456", 123.456*msecsPer31Days)