From 1a2b15a976db8cb0ff08144116c9f1c08a21950b Mon Sep 17 00:00:00 2001 From: vnxme <46669194+vnxme@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:05:03 +0300 Subject: [PATCH] Clock matcher: add time zone support --- .../gd_matcher_clock.caddytest | 67 +++++++++++++++++++ modules/l4clock/matcher.go | 38 ++++++++--- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/integration/caddyfile_adapt/gd_matcher_clock.caddytest b/integration/caddyfile_adapt/gd_matcher_clock.caddytest index fd6a7b5..a15e1a0 100644 --- a/integration/caddyfile_adapt/gd_matcher_clock.caddytest +++ b/integration/caddyfile_adapt/gd_matcher_clock.caddytest @@ -19,6 +19,16 @@ proxy 06.upstream.local:8080 07.upstream.local:8080 } } + :8888 { + @la_is_awake clock 08:00:00 20:00:00 America/Los_Angeles + route @la_is_awake { + proxy existing.machine.local:8888 + } + @la_is_asleep not clock 08:00:00 20:00:00 America/Los_Angeles + route @la_is_asleep { + proxy non-existing.machine.local:8888 + } + } } } ---------- @@ -146,6 +156,63 @@ ] } ] + }, + "srv1": { + "listen": [ + ":8888" + ], + "routes": [ + { + "match": [ + { + "clock": { + "after": "08:00:00", + "before": "20:00:00", + "timezone": "America/Los_Angeles" + } + } + ], + "handle": [ + { + "handler": "proxy", + "upstreams": [ + { + "dial": [ + "existing.machine.local:8888" + ] + } + ] + } + ] + }, + { + "match": [ + { + "not": [ + { + "clock": { + "after": "08:00:00", + "before": "20:00:00", + "timezone": "America/Los_Angeles" + } + } + ] + } + ], + "handle": [ + { + "handler": "proxy", + "upstreams": [ + { + "dial": [ + "non-existing.machine.local:8888" + ] + } + ] + } + ] + } + ] } } } diff --git a/modules/l4clock/matcher.go b/modules/l4clock/matcher.go index b0feac0..b59a068 100644 --- a/modules/l4clock/matcher.go +++ b/modules/l4clock/matcher.go @@ -17,6 +17,7 @@ package l4clock import ( "strings" "time" + _ "time/tzdata" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" @@ -30,9 +31,11 @@ func init() { // MatchClock is able to match any connections using the time when they are wrapped/matched. type MatchClock struct { - After string `json:"after,omitempty"` - Before string `json:"before,omitempty"` + After string `json:"after,omitempty"` + Before string `json:"before,omitempty"` + Timezone string `json:"timezone,omitempty"` + location *time.Location secondsAfter int secondsBefore int } @@ -53,22 +56,23 @@ func (m *MatchClock) Match(cx *layer4.Connection) (bool, error) { t = time.Now() repl.Set(timeKey, t) } - secondsNow := timeToSeconds(t.(time.Time)) + secondsNow := timeToSeconds(t.(time.Time).In(m.location)) if secondsNow >= m.secondsAfter && secondsNow < m.secondsBefore { return true, nil } return false, nil } -// Provision parses m's time points. +// Provision parses m's time points and a time zone (the system's local time zone is used by default). func (m *MatchClock) Provision(_ caddy.Context) (err error) { repl := caddy.NewReplacer() - after, before := repl.ReplaceAll(m.After, ""), repl.ReplaceAll(m.Before, "") + after := repl.ReplaceAll(m.After, "") if m.secondsAfter, err = timeParseSeconds(after, 0); err != nil { return } + before := repl.ReplaceAll(m.Before, "") if m.secondsBefore, err = timeParseSeconds(before, 0); err != nil { return } @@ -83,23 +87,31 @@ func (m *MatchClock) Provision(_ caddy.Context) (err error) { m.secondsAfter, m.secondsBefore = m.secondsBefore, m.secondsAfter } + timezone := repl.ReplaceAll(m.Timezone, "") + if timezone == "" { + m.location = time.Local + } else if m.location, err = time.LoadLocation(timezone); err != nil { + return + } + return nil } // UnmarshalCaddyfile sets up the MatchClock from Caddyfile tokens. Syntax: // -// clock -// clock -// clock +// clock [] +// clock [] +// clock [] // // Note: MatchClock checks if time_now is greater than or equal to time_after AND less than time_before. // The lowest value is 00:00:00. If time_before equals 00:00:00, it is treated as 24:00:00. If time_after is greater -// than time_before, they are swapped. Both "after 00:00:00" and "before 00:00:00" match all day. +// than time_before, they are swapped. Both "after 00:00:00" and "before 00:00:00" match all day. An IANA time zone +// location should be used as a value for time_zone. If time_zone is empty, the system's local time zone is used. func (m *MatchClock) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { _, wrapper := d.Next(), d.Val() // consume wrapper name - // Two same-line arguments must be provided - if d.CountRemainingArgs() != 2 { + // Only two or three same-line arguments are supported + if d.CountRemainingArgs() < 2 || d.CountRemainingArgs() > 3 { return d.ArgErr() } @@ -115,6 +127,10 @@ func (m *MatchClock) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { } m.After, m.Before = first, second + if d.NextArg() { + m.Timezone = d.Val() + } + // No blocks are supported if d.NextBlock(d.Nesting()) { return d.Errf("malformed %s matcher: blocks are not supported", wrapper)