From 95c9b630cc76ae3c98cefb02d633acded2d3ed0e Mon Sep 17 00:00:00 2001 From: Nikolay Date: Tue, 15 Dec 2020 13:51:12 +0300 Subject: [PATCH] adds new Array Flags (#965) * adds ArrayDuration and ArrayBool flags, makes sendTimeout and tlsInsecure configurable per remoteWrite url * added backward compatibility testcases for ArrayDuration and ArrayBool * fixes bool flag * fixes test cases --- app/vmagent/remotewrite/client.go | 9 +- lib/flagutil/array.go | 99 ++++++++++++++++++++++ lib/flagutil/array_test.go | 132 +++++++++++++++++++++++++++++- 3 files changed, 233 insertions(+), 7 deletions(-) diff --git a/app/vmagent/remotewrite/client.go b/app/vmagent/remotewrite/client.go index a169f774b..2ffdfab12 100644 --- a/app/vmagent/remotewrite/client.go +++ b/app/vmagent/remotewrite/client.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/tls" "encoding/base64" - "flag" "fmt" "io/ioutil" "net/http" @@ -21,11 +20,11 @@ import ( ) var ( - sendTimeout = flag.Duration("remoteWrite.sendTimeout", time.Minute, "Timeout for sending a single block of data to -remoteWrite.url") + sendTimeout = flagutil.NewArrayDuration("remoteWrite.sendTimeout", "Timeout for sending a single block of data to -remoteWrite.url") proxyURL = flagutil.NewArray("remoteWrite.proxyURL", "Optional proxy URL for writing data to -remoteWrite.url. Supported proxies: http, https, socks5. "+ "Example: -remoteWrite.proxyURL=socks5://proxy:1234") - tlsInsecureSkipVerify = flag.Bool("remoteWrite.tlsInsecureSkipVerify", false, "Whether to skip tls verification when connecting to -remoteWrite.url") + tlsInsecureSkipVerify = flagutil.NewArrayBool("remoteWrite.tlsInsecureSkipVerify", "Whether to skip tls verification when connecting to -remoteWrite.url") tlsCertFile = flagutil.NewArray("remoteWrite.tlsCertFile", "Optional path to client-side TLS certificate file to use when connecting to -remoteWrite.url. "+ "If multiple args are set, then they are applied independently for the corresponding -remoteWrite.url") tlsKeyFile = flagutil.NewArray("remoteWrite.tlsKeyFile", "Optional path to client-side TLS certificate key to use when connecting to -remoteWrite.url. "+ @@ -108,7 +107,7 @@ func newClient(argIdx int, remoteWriteURL, sanitizedURL string, fq *persistentqu fq: fq, hc: &http.Client{ Transport: tr, - Timeout: *sendTimeout, + Timeout: sendTimeout.GetOptionalArgOrDefault(argIdx, time.Minute), }, stopCh: make(chan struct{}), } @@ -140,7 +139,7 @@ func getTLSConfig(argIdx int) (*tls.Config, error) { CertFile: tlsCertFile.GetOptionalArg(argIdx), KeyFile: tlsKeyFile.GetOptionalArg(argIdx), ServerName: tlsServerName.GetOptionalArg(argIdx), - InsecureSkipVerify: *tlsInsecureSkipVerify, + InsecureSkipVerify: tlsInsecureSkipVerify.GetOptionalArg(argIdx), } if c.CAFile == "" && c.CertFile == "" && c.KeyFile == "" && c.ServerName == "" && !c.InsecureSkipVerify { return nil, nil diff --git a/lib/flagutil/array.go b/lib/flagutil/array.go index d4454795f..51f7d2f5b 100644 --- a/lib/flagutil/array.go +++ b/lib/flagutil/array.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" "strings" + "time" ) // NewArray returns new Array with the given name and description. @@ -16,6 +17,24 @@ func NewArray(name, description string) *Array { return &a } +// NewArrayDuration returns new ArrayDuration with the given name and description. +func NewArrayDuration(name, description string) *ArrayDuration { + description += "\nSupports `array` of values separated by comma" + + " or specified via multiple flags." + var a ArrayDuration + flag.Var(&a, name, description) + return &a +} + +// 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." + var a ArrayBool + flag.Var(&a, name, description) + return &a +} + // Array is a flag that holds an array of values. // // It may be set either by specifying multiple flags with the given name @@ -124,3 +143,83 @@ func (a *Array) GetOptionalArg(argIdx int) string { } return x[argIdx] } + +// ArrayBool is a flag that holds an array of booleans values. +// have the same api as Array. +type ArrayBool []bool + +// IsBoolFlag implements flag.IsBoolFlag interface +func (a *ArrayBool) IsBoolFlag() bool { return true } + +// String implements flag.Value interface +func (a *ArrayBool) String() string { + formattedBools := make([]string, len(*a)) + for i, v := range *a { + formattedBools[i] = strconv.FormatBool(v) + } + return strings.Join(formattedBools, ",") +} + +// Set implements flag.Value interface +func (a *ArrayBool) Set(value string) error { + values := parseArrayValues(value) + for _, v := range values { + b, err := strconv.ParseBool(v) + if err != nil { + return err + } + *a = append(*a, b) + } + return nil +} + +// GetOptionalArg returns optional arg under the given argIdx. +func (a *ArrayBool) GetOptionalArg(argIdx int) bool { + x := *a + if argIdx >= len(x) { + if len(x) == 1 { + return x[0] + } + return false + } + return x[argIdx] +} + +// ArrayDuration is a flag that holds an array of time.Duration values. +// have the same api as Array. +type ArrayDuration []time.Duration + +// String implements flag.Value interface +func (a *ArrayDuration) String() string { + formattedBools := make([]string, len(*a)) + for i, v := range *a { + formattedBools[i] = v.String() + } + return strings.Join(formattedBools, ",") +} + +// Set implements flag.Value interface +func (a *ArrayDuration) Set(value string) error { + values := parseArrayValues(value) + for _, v := range values { + b, err := time.ParseDuration(v) + if err != nil { + return err + } + *a = append(*a, b) + } + return nil +} + +// GetOptionalArgOrDefault returns optional arg under the given argIdx, +// or default value, if argIdx not found. +func (a *ArrayDuration) GetOptionalArgOrDefault(argIdx int, defaultValue time.Duration) time.Duration { + x := *a + if argIdx >= len(x) { + if len(x) == 1 { + return x[0] + } + return defaultValue + } + return x[argIdx] +} diff --git a/lib/flagutil/array_test.go b/lib/flagutil/array_test.go index a29e59e17..7e27ab2ff 100644 --- a/lib/flagutil/array_test.go +++ b/lib/flagutil/array_test.go @@ -5,13 +5,21 @@ import ( "os" "reflect" "testing" + "time" ) -var fooFlag Array +var ( + fooFlag Array + fooFlagDuration ArrayDuration + fooFlagBool ArrayBool +) func init() { - os.Args = append(os.Args, "--fooFlag=foo", "--fooFlag=bar") + os.Args = append(os.Args, "--fooFlag=foo", "--fooFlag=bar", "--fooFlagDuration=10s", "--fooFlagDuration=5m") + os.Args = append(os.Args, "--fooFlagBool=true", "--fooFlagBool=false,true", "--fooFlagBool") flag.Var(&fooFlag, "fooFlag", "test") + flag.Var(&fooFlagDuration, "fooFlagDuration", "test") + flag.Var(&fooFlagBool, "fooFlagBool", "test") } func TestMain(m *testing.M) { @@ -91,3 +99,123 @@ func TestArrayString(t *testing.T) { f(`", foo","b\"ar",`) f(`,"\nfoo\\",bar`) } + +func TestArrayDuration(t *testing.T) { + expected := map[time.Duration]struct{}{ + time.Second * 10: {}, + time.Minute * 5: {}, + } + if len(expected) != len(fooFlagDuration) { + t.Errorf("len array flag (%d) is not equal to %d", len(fooFlag), len(expected)) + } + for _, i := range fooFlagDuration { + if _, ok := expected[i]; !ok { + t.Errorf("unexpected item in array %v", i) + } + } +} + +func TestArrayDurationSet(t *testing.T) { + f := func(s string, expectedValues []time.Duration) { + t.Helper() + var a ArrayDuration + _ = a.Set(s) + if !reflect.DeepEqual([]time.Duration(a), expectedValues) { + t.Fatalf("unexpected values parsed;\ngot\n%q\nwant\n%q", a, expectedValues) + } + } + f("", nil) + f(`1m`, []time.Duration{time.Minute}) + f(`5m,1s,1h`, []time.Duration{time.Minute * 5, time.Second, time.Hour}) +} + +func TestArrayDurationGetOptionalArg(t *testing.T) { + f := func(s string, argIdx int, expectedValue time.Duration, defaultValue time.Duration) { + t.Helper() + var a ArrayDuration + _ = a.Set(s) + v := a.GetOptionalArgOrDefault(argIdx, defaultValue) + if v != expectedValue { + t.Fatalf("unexpected value; got %q; want %q", v, expectedValue) + } + } + f("", 0, time.Second, time.Second) + f("", 1, time.Minute, time.Minute) + f("10s,1m", 1, time.Minute, time.Minute) + f("10s", 3, time.Second*10, time.Minute) +} + +func TestArrayDurationString(t *testing.T) { + f := func(s string) { + t.Helper() + var a ArrayDuration + _ = a.Set(s) + result := a.String() + if result != s { + t.Fatalf("unexpected string;\ngot\n%s\nwant\n%s", result, s) + } + } + f("") + f("10s,1m0s") + f("5m0s,1s") +} + +func TestArrayBool(t *testing.T) { + expected := []bool{ + true, false, true, true, + } + if len(expected) != len(fooFlagBool) { + t.Errorf("len array flag (%d) is not equal to %d", len(fooFlag), len(expected)) + } + for i, v := range fooFlagBool { + if v != expected[i] { + t.Errorf("unexpected item in array index=%v,value=%v,want=%v", i, v, expected[i]) + } + } +} + +func TestArrayBoolSet(t *testing.T) { + f := func(s string, expectedValues []bool) { + t.Helper() + var a ArrayBool + _ = a.Set(s) + if !reflect.DeepEqual([]bool(a), expectedValues) { + t.Fatalf("unexpected values parsed;\ngot\n%v\nwant\n%v", a, expectedValues) + } + } + f("", nil) + f(`true`, []bool{true}) + f(`false,True,False`, []bool{false, true, false}) +} + +func TestArrayBoolGetOptionalArg(t *testing.T) { + f := func(s string, argIdx int, expectedValue bool) { + t.Helper() + var a ArrayBool + _ = a.Set(s) + v := a.GetOptionalArg(argIdx) + if v != expectedValue { + t.Fatalf("unexpected value; got %v; want %v", v, expectedValue) + } + } + f("", 0, false) + f("", 1, false) + f("true,true,false", 1, true) + f("true", 2, true) +} + +func TestArrayBoolString(t *testing.T) { + f := func(s string) { + t.Helper() + var a ArrayBool + _ = a.Set(s) + result := a.String() + if result != s { + t.Fatalf("unexpected string;\ngot\n%s\nwant\n%s", result, s) + } + } + f("") + f("true") + f("true,false") + f("false,true") +}