git.iko.soy/typeable/oauth2-proxy
on master help sign in
Introduce Duration so that marshalling works for duration strings
commitb6d6f31ac1fd33b93ab71a1a06d7f528740354ae authorJoel Speed <joel.speed@hotmail.co.uk> dateWed 11 Nov 2020, 23:02 (6 years ago) parented92df3
9 files changed+153−18
pkg/apis/options/common.go+48 pkg/apis/options/common_test.go+88 pkg/apis/options/legacy_options.go+2−1 pkg/apis/options/legacy_options_test.go+4−4 pkg/apis/options/upstreams.go+1−3 pkg/upstream/http.go+1−1 pkg/upstream/http_test.go+7−7 pkg/validation/upstreams.go+1−1 pkg/validation/upstreams_test.go+1−1
pkg/apis/options/common.go+48
@@ -1,5 +1,11 @@
11package options
22
3+import (
4+ "fmt"
5+ "strconv"
6+ "time"
7+)
8+
39// SecretSource references an individual secret value.
410// Only one source within the struct should be defined at any time.
511type SecretSource struct {
@@ -12,3 +18,45 @@ type SecretSource struct {
1218 // FromFile expects a path to a file containing the secret value.
1319 FromFile string
1420}
21+
22+// Duration is an alias for time.Duration so that we can ensure the marshalling
23+// and unmarshalling of string durations is done as users expect.
24+// Intentional blank line below to keep this first part of the comment out of
25+// any generated references.
26+
27+// Duration is as string representation of a period of time.
28+// A duration string is a is a possibly signed sequence of decimal numbers,
29+// each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
30+// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
31+type Duration time.Duration
32+
33+// UnmarshalJSON parses the duration string and sets the value of duration
34+// to the value of the duration string.
35+func (d *Duration) UnmarshalJSON(data []byte) error {
36+ input := string(data)
37+ if unquoted, err := strconv.Unquote(input); err == nil {
38+ input = unquoted
39+ }
40+
41+ du, err := time.ParseDuration(input)
42+ if err != nil {
43+ return err
44+ }
45+ *d = Duration(du)
46+ return nil
47+}
48+
49+// MarshalJSON ensures that when the string is marshalled to JSON as a human
50+// readable string.
51+func (d *Duration) MarshalJSON() ([]byte, error) {
52+ dStr := fmt.Sprintf("%q", d.Duration().String())
53+ return []byte(dStr), nil
54+}
55+
56+// Duration returns the time.Duration version of this Duration
57+func (d *Duration) Duration() time.Duration {
58+ if d == nil {
59+ return time.Duration(0)
60+ }
61+ return time.Duration(*d)
62+}
pkg/apis/options/common_test.gonew file+88
@@ -0,0 +1,88 @@
1+package options
2+
3+import (
4+ "encoding/json"
5+ "errors"
6+ "time"
7+
8+ . "github.com/onsi/ginkgo"
9+ . "github.com/onsi/ginkgo/extensions/table"
10+ . "github.com/onsi/gomega"
11+)
12+
13+var _ = Describe("Common", func() {
14+ Context("Duration", func() {
15+ type marshalJSONTableInput struct {
16+ duration Duration
17+ expectedJSON string
18+ }
19+
20+ DescribeTable("MarshalJSON",
21+ func(in marshalJSONTableInput) {
22+ data, err := in.duration.MarshalJSON()
23+ Expect(err).ToNot(HaveOccurred())
24+ Expect(string(data)).To(Equal(in.expectedJSON))
25+
26+ var d Duration
27+ Expect(json.Unmarshal(data, &d)).To(Succeed())
28+ Expect(d).To(Equal(in.duration))
29+ },
30+ Entry("30 seconds", marshalJSONTableInput{
31+ duration: Duration(30 * time.Second),
32+ expectedJSON: "\"30s\"",
33+ }),
34+ Entry("1 minute", marshalJSONTableInput{
35+ duration: Duration(1 * time.Minute),
36+ expectedJSON: "\"1m0s\"",
37+ }),
38+ Entry("1 hour 15 minutes", marshalJSONTableInput{
39+ duration: Duration(75 * time.Minute),
40+ expectedJSON: "\"1h15m0s\"",
41+ }),
42+ Entry("A zero Duration", marshalJSONTableInput{
43+ duration: Duration(0),
44+ expectedJSON: "\"0s\"",
45+ }),
46+ )
47+
48+ type unmarshalJSONTableInput struct {
49+ json string
50+ expectedErr error
51+ expectedDuration Duration
52+ }
53+
54+ DescribeTable("UnmarshalJSON",
55+ func(in unmarshalJSONTableInput) {
56+ // A duration must be initialised pointer before UnmarshalJSON will work.
57+ zero := Duration(0)
58+ d := &zero
59+
60+ err := d.UnmarshalJSON([]byte(in.json))
61+ if in.expectedErr != nil {
62+ Expect(err).To(MatchError(in.expectedErr.Error()))
63+ } else {
64+ Expect(err).ToNot(HaveOccurred())
65+ }
66+ Expect(d).ToNot(BeNil())
67+ Expect(*d).To(Equal(in.expectedDuration))
68+ },
69+ Entry("1m", unmarshalJSONTableInput{
70+ json: "\"1m\"",
71+ expectedDuration: Duration(1 * time.Minute),
72+ }),
73+ Entry("30s", unmarshalJSONTableInput{
74+ json: "\"30s\"",
75+ expectedDuration: Duration(30 * time.Second),
76+ }),
77+ Entry("1h15m", unmarshalJSONTableInput{
78+ json: "\"1h15m\"",
79+ expectedDuration: Duration(75 * time.Minute),
80+ }),
81+ Entry("am", unmarshalJSONTableInput{
82+ json: "\"am\"",
83+ expectedErr: errors.New("time: invalid duration \"am\""),
84+ expectedDuration: Duration(0),
85+ }),
86+ )
87+ })
88+})
pkg/apis/options/legacy_options.go+2−1
@@ -84,6 +84,7 @@ func (l *LegacyUpstreams) convert() (Upstreams, error) {
8484 u.Path = "/"
8585 }
8686
87+ flushInterval := Duration(l.FlushInterval)
8788 upstream := Upstream{
8889 ID: u.Path,
8990 Path: u.Path,
@@ -91,7 +92,7 @@ func (l *LegacyUpstreams) convert() (Upstreams, error) {
9192 InsecureSkipTLSVerify: l.SSLUpstreamInsecureSkipVerify,
9293 PassHostHeader: &l.PassHostHeader,
9394 ProxyWebSockets: &l.ProxyWebSockets,
94− FlushInterval: &l.FlushInterval,
95+ FlushInterval: &flushInterval,
9596 }
9697
9798 switch u.Scheme {
pkg/apis/options/legacy_options_test.go+4−4
@@ -17,8 +17,8 @@ var _ = Describe("Legacy Options", func() {
1717 legacyOpts := NewLegacyOptions()
1818
1919 // Set upstreams and related options to test their conversion
20− flushInterval := 5 * time.Second
21− legacyOpts.LegacyUpstreams.FlushInterval = flushInterval
20+ flushInterval := Duration(5 * time.Second)
21+ legacyOpts.LegacyUpstreams.FlushInterval = time.Duration(flushInterval)
2222 legacyOpts.LegacyUpstreams.PassHostHeader = true
2323 legacyOpts.LegacyUpstreams.ProxyWebSockets = true
2424 legacyOpts.LegacyUpstreams.SSLUpstreamInsecureSkipVerify = true
@@ -124,7 +124,7 @@ var _ = Describe("Legacy Options", func() {
124124 skipVerify := true
125125 passHostHeader := false
126126 proxyWebSockets := true
127− flushInterval := 5 * time.Second
127+ flushInterval := Duration(5 * time.Second)
128128
129129 // Test cases and expected outcomes
130130 validHTTP := "http://foo.bar/baz"
@@ -199,7 +199,7 @@ var _ = Describe("Legacy Options", func() {
199199 SSLUpstreamInsecureSkipVerify: skipVerify,
200200 PassHostHeader: passHostHeader,
201201 ProxyWebSockets: proxyWebSockets,
202− FlushInterval: flushInterval,
202+ FlushInterval: time.Duration(flushInterval),
203203 }
204204
205205 upstreams, err := legacyUpstreams.convert()
pkg/apis/options/upstreams.go+1−3
@@ -1,7 +1,5 @@
11package options
22
3−import "time"
4−
53// Upstreams is a collection of definitions for upstream servers.
64type Upstreams []Upstream
75
@@ -47,7 +45,7 @@ type Upstream struct {
4745 // FlushInterval is the period between flushing the response buffer when
4846 // streaming response from the upstream.
4947 // Defaults to 1 second.
50− FlushInterval *time.Duration `json:"flushInterval,omitempty"`
48+ FlushInterval *Duration `json:"flushInterval,omitempty"`
5149
5250 // PassHostHeader determines whether the request host header should be proxied
5351 // to the upstream server.
pkg/upstream/http.go+1−1
@@ -98,7 +98,7 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
9898
9999 // Configure options on the SingleHostReverseProxy
100100 if upstream.FlushInterval != nil {
101− proxy.FlushInterval = *upstream.FlushInterval
101+ proxy.FlushInterval = upstream.FlushInterval.Duration()
102102 } else {
103103 proxy.FlushInterval = 1 * time.Second
104104 }
pkg/upstream/http_test.go+7−7
@@ -22,8 +22,8 @@ import (
2222
2323var _ = Describe("HTTP Upstream Suite", func() {
2424
25− const flushInterval5s = 5 * time.Second
26− const flushInterval1s = 1 * time.Second
25+ const flushInterval5s = options.Duration(5 * time.Second)
26+ const flushInterval1s = options.Duration(1 * time.Second)
2727 truth := true
2828 falsum := false
2929
@@ -52,7 +52,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
5252
5353 rw := httptest.NewRecorder()
5454
55− flush := 1 * time.Second
55+ flush := options.Duration(1 * time.Second)
5656
5757 upstream := options.Upstream{
5858 ID: in.id,
@@ -258,7 +258,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
258258 req := httptest.NewRequest("", "http://example.localhost/foo", nil)
259259 rw := httptest.NewRecorder()
260260
261− flush := 1 * time.Second
261+ flush := options.Duration(1 * time.Second)
262262 upstream := options.Upstream{
263263 ID: "noPassHost",
264264 PassHostHeader: &falsum,
@@ -290,7 +290,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
290290
291291 type newUpstreamTableInput struct {
292292 proxyWebSockets bool
293− flushInterval time.Duration
293+ flushInterval options.Duration
294294 skipVerify bool
295295 sigData *options.SignatureData
296296 errorHandler func(http.ResponseWriter, *http.Request, error)
@@ -319,7 +319,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
319319
320320 proxy, ok := upstreamProxy.handler.(*httputil.ReverseProxy)
321321 Expect(ok).To(BeTrue())
322− Expect(proxy.FlushInterval).To(Equal(in.flushInterval))
322+ Expect(proxy.FlushInterval).To(Equal(in.flushInterval.Duration()))
323323 Expect(proxy.ErrorHandler != nil).To(Equal(in.errorHandler != nil))
324324 if in.skipVerify {
325325 Expect(proxy.Transport).To(Equal(&http.Transport{
@@ -370,7 +370,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
370370 var proxyServer *httptest.Server
371371
372372 BeforeEach(func() {
373− flush := 1 * time.Second
373+ flush := options.Duration(1 * time.Second)
374374 upstream := options.Upstream{
375375 ID: "websocketProxy",
376376 PassHostHeader: &truth,
pkg/validation/upstreams.go+1−1
@@ -70,7 +70,7 @@ func validateStaticUpstream(upstream options.Upstream) []string {
7070 if upstream.InsecureSkipTLSVerify {
7171 msgs = append(msgs, fmt.Sprintf("upstream %q has insecureSkipTLSVerify, but is a static upstream, this will have no effect.", upstream.ID))
7272 }
73− if upstream.FlushInterval != nil && *upstream.FlushInterval != time.Second {
73+ if upstream.FlushInterval != nil && upstream.FlushInterval.Duration() != time.Second {
7474 msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID))
7575 }
7676 if upstream.PassHostHeader != nil {
pkg/validation/upstreams_test.go+1−1
@@ -15,7 +15,7 @@ var _ = Describe("Upstreams", func() {
1515 errStrings []string
1616 }
1717
18− flushInterval := 5 * time.Second
18+ flushInterval := options.Duration(5 * time.Second)
1919 staticCode200 := 200
2020 truth := true
2121