Skip to content

Commit

Permalink
Clock matcher: add time zone support
Browse files Browse the repository at this point in the history
  • Loading branch information
vnxme committed Aug 2, 2024
1 parent 26c420d commit 1a2b15a
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 11 deletions.
67 changes: 67 additions & 0 deletions integration/caddyfile_adapt/gd_matcher_clock.caddytest
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
----------
Expand Down Expand Up @@ -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"
]
}
]
}
]
}
]
}
}
}
Expand Down
38 changes: 27 additions & 11 deletions modules/l4clock/matcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package l4clock
import (
"strings"
"time"
_ "time/tzdata"

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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 <time_after> <time_before>
// clock <after|from> <time_after>
// clock <before|till|to|until> <time_before>
// clock <time_after> <time_before> [<time_zone>]
// clock <after|from> <time_after> [<time_zone>]
// clock <before|till|to|until> <time_before> [<time_zone>]
//
// 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()
}

Expand All @@ -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)
Expand Down

0 comments on commit 1a2b15a

Please sign in to comment.