Skip to content

Commit d35d429

Browse files
authored
Fix nginxRetries and timeout accepts a negative value
* Bug introduced in: b499ca7
1 parent 4ef2b34 commit d35d429

File tree

2 files changed

+97
-23
lines changed

2 files changed

+97
-23
lines changed

exporter.go

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"crypto/tls"
55
"flag"
6+
"fmt"
67
"log"
78
"net/http"
89
"os"
@@ -26,16 +27,16 @@ func getEnv(key, defaultValue string) string {
2627
return value
2728
}
2829

29-
func getEnvInt(key string, defaultValue int) int {
30+
func getEnvUint(key string, defaultValue uint) uint {
3031
value, ok := os.LookupEnv(key)
3132
if !ok {
3233
return defaultValue
3334
}
34-
i, err := strconv.ParseInt(value, 10, 64)
35+
i, err := strconv.ParseUint(value, 10, 64)
3536
if err != nil {
36-
log.Fatalf("Environment variable value for %s must be an int: %v", key, err)
37+
log.Fatalf("Environment variable value for %s must be an uint: %v", key, err)
3738
}
38-
return int(i)
39+
return uint(i)
3940
}
4041

4142
func getEnvBool(key string, defaultValue bool) bool {
@@ -50,23 +51,53 @@ func getEnvBool(key string, defaultValue bool) bool {
5051
return b
5152
}
5253

53-
func getEnvDuration(key string, defaultValue time.Duration) time.Duration {
54+
func getEnvPositiveDuration(key string, defaultValue time.Duration) positiveDuration {
5455
value, ok := os.LookupEnv(key)
5556
if !ok {
56-
return defaultValue
57+
return positiveDuration{defaultValue}
5758
}
58-
d, err := time.ParseDuration(value)
59+
60+
posDur, err := parsePositiveDuration(value)
5961
if err != nil {
60-
log.Fatalf("Environment variable value for %s must be a duration: %v", key, err)
62+
log.Fatalf("Environment variable value for %s must be a positive duration: %v", key, err)
6163
}
62-
return d
64+
return posDur
6365
}
6466

65-
func createClientWithRetries(getClient func() (interface{}, error), retries int, retryInterval time.Duration) (interface{}, error) {
67+
// positiveDuration is a wrapper of time.Duration to ensure only positive values are accepted
68+
type positiveDuration struct{ time.Duration }
69+
70+
func (pd *positiveDuration) Set(s string) error {
71+
dur, err := parsePositiveDuration(s)
72+
if err != nil {
73+
return err
74+
}
75+
76+
pd.Duration = dur.Duration
77+
return nil
78+
}
79+
80+
func parsePositiveDuration(s string) (positiveDuration, error) {
81+
dur, err := time.ParseDuration(s)
82+
if err != nil {
83+
return positiveDuration{}, err
84+
}
85+
if dur < 0 {
86+
return positiveDuration{}, fmt.Errorf("negative duration %v is not valid", dur)
87+
}
88+
return positiveDuration{dur}, nil
89+
}
90+
91+
func createPositiveDurationFlag(name string, value positiveDuration, usage string) *positiveDuration {
92+
flag.Var(&value, name, usage)
93+
return &value
94+
}
95+
96+
func createClientWithRetries(getClient func() (interface{}, error), retries uint, retryInterval time.Duration) (interface{}, error) {
6697
var err error
6798
var nginxClient interface{}
6899

69-
for i := retries; i >= 0; i-- {
100+
for i := 0; i <= int(retries); i++ {
70101
nginxClient, err = getClient()
71102
if err == nil {
72103
return nginxClient, nil
@@ -90,9 +121,9 @@ var (
90121
defaultNginxPlus = getEnvBool("NGINX_PLUS", false)
91122
defaultScrapeURI = getEnv("SCRAPE_URI", "http://127.0.0.1:8080/stub_status")
92123
defaultSslVerify = getEnvBool("SSL_VERIFY", true)
93-
defaultTimeout = getEnvDuration("TIMEOUT", time.Second*5)
94-
defaultNginxRetries = getEnvInt("NGINX_RETRIES", 0)
95-
defaultNginxRetryInterval = getEnvDuration("NGINX_RETRY_INTERVAL", time.Second*5)
124+
defaultTimeout = getEnvPositiveDuration("TIMEOUT", time.Second*5)
125+
defaultNginxRetries = getEnvUint("NGINX_RETRIES", 0)
126+
defaultNginxRetryInterval = getEnvPositiveDuration("NGINX_RETRY_INTERVAL", time.Second*5)
96127

97128
// Command-line flags
98129
listenAddr = flag.String("web.listen-address",
@@ -111,13 +142,16 @@ var (
111142
sslVerify = flag.Bool("nginx.ssl-verify",
112143
defaultSslVerify,
113144
"Perform SSL certificate verification. The default value can be overwritten by SSL_VERIFY environment variable.")
114-
timeout = flag.Duration("nginx.timeout",
115-
defaultTimeout,
116-
"A timeout for scraping metrics from NGINX or NGINX Plus. The default value can be overwritten by TIMEOUT environment variable.")
117-
nginxRetries = flag.Int("nginx.retries",
145+
nginxRetries = flag.Uint("nginx.retries",
118146
defaultNginxRetries,
119147
"A number of retries the exporter will make on start to connect to the NGINX stub_status page/NGINX Plus API before exiting with an error. The default value can be overwritten by NGINX_RETRIES environment variable.")
120-
nginxRetryInterval = flag.Duration("nginx.retry-interval",
148+
149+
// Custom command-line flags
150+
timeout = createPositiveDurationFlag("nginx.timeout",
151+
defaultTimeout,
152+
"A timeout for scraping metrics from NGINX or NGINX Plus. The default value can be overwritten by TIMEOUT environment variable.")
153+
154+
nginxRetryInterval = createPositiveDurationFlag("nginx.retry-interval",
121155
defaultNginxRetryInterval,
122156
"An interval between retries to connect to the NGINX stub_status page/NGINX Plus API on start. The default value can be overwritten by NGINX_RETRY_INTERVAL environment variable.")
123157
)
@@ -144,7 +178,7 @@ func main() {
144178
registry.MustRegister(buildInfoMetric)
145179

146180
httpClient := &http.Client{
147-
Timeout: *timeout,
181+
Timeout: timeout.Duration,
148182
Transport: &http.Transport{
149183
TLSClientConfig: &tls.Config{InsecureSkipVerify: !*sslVerify},
150184
},
@@ -160,15 +194,15 @@ func main() {
160194
if *nginxPlus {
161195
plusClient, err := createClientWithRetries(func() (interface{}, error) {
162196
return plusclient.NewNginxClient(httpClient, *scrapeURI)
163-
}, *nginxRetries, *nginxRetryInterval)
197+
}, *nginxRetries, nginxRetryInterval.Duration)
164198
if err != nil {
165199
log.Fatalf("Could not create Nginx Plus Client: %v", err)
166200
}
167201
registry.MustRegister(collector.NewNginxPlusCollector(plusClient.(*plusclient.NginxClient), "nginxplus"))
168202
} else {
169203
ossClient, err := createClientWithRetries(func() (interface{}, error) {
170204
return client.NewNginxClient(httpClient, *scrapeURI)
171-
}, *nginxRetries, *nginxRetryInterval)
205+
}, *nginxRetries, nginxRetryInterval.Duration)
172206
if err != nil {
173207
log.Fatalf("Could not create Nginx Client: %v", err)
174208
}

exporter_test.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func TestCreateClientWithRetries(t *testing.T) {
1111
type args struct {
1212
client interface{}
1313
err error
14-
retries int
14+
retries uint
1515
retryInterval time.Duration
1616
}
1717

@@ -81,3 +81,43 @@ func TestCreateClientWithRetries(t *testing.T) {
8181
})
8282
}
8383
}
84+
85+
func TestParsePositiveDuration(t *testing.T) {
86+
tests := []struct {
87+
name string
88+
testInput string
89+
want positiveDuration
90+
wantErr bool
91+
}{
92+
{
93+
"ParsePositiveDuration returns a positiveDuration",
94+
"15ms",
95+
positiveDuration{15 * time.Millisecond},
96+
false,
97+
},
98+
{
99+
"ParsePositiveDuration returns error for trying to parse negative value",
100+
"-15ms",
101+
positiveDuration{},
102+
true,
103+
},
104+
{
105+
"ParsePositiveDuration returns error for trying to parse empty string",
106+
"",
107+
positiveDuration{},
108+
true,
109+
},
110+
}
111+
for _, tt := range tests {
112+
t.Run(tt.name, func(t *testing.T) {
113+
got, err := parsePositiveDuration(tt.testInput)
114+
if (err != nil) != tt.wantErr {
115+
t.Errorf("parsePositiveDuration() error = %v, wantErr %v", err, tt.wantErr)
116+
return
117+
}
118+
if !reflect.DeepEqual(got, tt.want) {
119+
t.Errorf("parsePositiveDuration() = %v, want %v", got, tt.want)
120+
}
121+
})
122+
}
123+
}

0 commit comments

Comments
 (0)