From 043eb1d9e5db456b9b78c0423cb44716fc81a932 Mon Sep 17 00:00:00 2001 From: dev Date: Tue, 14 May 2019 10:35:41 -0400 Subject: move internal packages to pkg folder and update reverse proxy * set automatic https error type for cert-magic failures * add state to onload and unload methods * update reverse proxy to use Provision() and Cleanup() --- cmd/caddy2/main.go | 1 - context.go | 1 + go.mod | 1 + go.sum | 2 + internal/caddyscript/lib/http.go | 72 ---------------- internal/caddyscript/lib/lib.go | 11 --- internal/caddyscript/lib/regex.go | 50 ----------- internal/caddyscript/lib/time.go | 130 ----------------------------- internal/caddyscript/matcherenv.go | 18 ---- modules/caddyhttp/caddyhttp.go | 13 ++- modules/caddyhttp/matchers.go | 2 +- modules/caddyhttp/reverseproxy/upstream.go | 29 +++++-- pkg/caddyscript/lib/http.go | 97 +++++++++++++++++++++ pkg/caddyscript/lib/regex.go | 50 +++++++++++ pkg/caddyscript/lib/time.go | 130 +++++++++++++++++++++++++++++ pkg/caddyscript/matcherenv.go | 18 ++++ 16 files changed, 333 insertions(+), 292 deletions(-) delete mode 100644 internal/caddyscript/lib/http.go delete mode 100644 internal/caddyscript/lib/lib.go delete mode 100644 internal/caddyscript/lib/regex.go delete mode 100644 internal/caddyscript/lib/time.go delete mode 100644 internal/caddyscript/matcherenv.go create mode 100644 pkg/caddyscript/lib/http.go create mode 100644 pkg/caddyscript/lib/regex.go create mode 100644 pkg/caddyscript/lib/time.go create mode 100644 pkg/caddyscript/matcherenv.go diff --git a/cmd/caddy2/main.go b/cmd/caddy2/main.go index 482c4f5..81f670b 100644 --- a/cmd/caddy2/main.go +++ b/cmd/caddy2/main.go @@ -4,7 +4,6 @@ import ( caddycmd "bitbucket.org/lightcodelabs/caddy2/cmd" // this is where modules get plugged in - _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog" _ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/reverseproxy" diff --git a/context.go b/context.go index cfb183a..e437f08 100644 --- a/context.go +++ b/context.go @@ -39,6 +39,7 @@ func NewContext(ctx Context) (Context, context.CancelFunc) { c, cancel := context.WithCancel(ctx.Context) wrappedCancel := func() { cancel() + for modName, modInstances := range newCtx.moduleInstances { for _, inst := range modInstances { if cu, ok := inst.(CleanerUpper); ok { diff --git a/go.mod b/go.mod index 89aa72e..b7ab09d 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-acme/lego v2.5.0+incompatible github.com/klauspost/cpuid v1.2.1 github.com/mholt/certmagic v0.5.1 + github.com/starlight-go/starlight v0.0.0-20181207205707-b06f321544f3 go.starlark.net v0.0.0-20190506145734-95b2783e7d63 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c ) diff --git a/go.sum b/go.sum index 7b32c47..414f6f2 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/mholt/certmagic v0.5.1/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9 github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/starlight-go/starlight v0.0.0-20181207205707-b06f321544f3 h1:/fBh1Ot84ILt/ociFHO98wJ9LxIMA3UG8B0unUJPFpY= +github.com/starlight-go/starlight v0.0.0-20181207205707-b06f321544f3/go.mod h1:pxOc2ZuBV+CNlQgzq/HJ9Z9G/eoEMHFeuGohOvva4Co= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= go.starlark.net v0.0.0-20190506145734-95b2783e7d63 h1:WVWIwnUjRw+Tf2iLM4GbAKbSteS4EOhS3z5qXOXN3Vk= diff --git a/internal/caddyscript/lib/http.go b/internal/caddyscript/lib/http.go deleted file mode 100644 index 233b43f..0000000 --- a/internal/caddyscript/lib/http.go +++ /dev/null @@ -1,72 +0,0 @@ -package caddyscript - -import ( - "fmt" - "net/http" - - "go.starlark.net/starlark" -) - -// HTTPRequest represents an http request type in caddyscript. -type HTTPRequest struct{ Req *http.Request } - -// AttrNames defines what properties and methods are available on the HTTPRequest type. -func (r HTTPRequest) AttrNames() []string { - return []string{"header", "query", "url", "method", "host", "tls"} -} - -func (r HTTPRequest) Freeze() {} -func (r HTTPRequest) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: HTTPRequest") } -func (r HTTPRequest) String() string { return fmt.Sprint(r.Req) } -func (r HTTPRequest) Type() string { return "HTTPRequest" } -func (r HTTPRequest) Truth() starlark.Bool { return true } - -// Header handles returning a header key. -func (r HTTPRequest) Header(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - var key string - err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &key) - if err != nil { - return starlark.None, fmt.Errorf("get request header: %v", err.Error()) - } - - return starlark.String(r.Req.Header.Get(key)), nil -} - -// Attr defines what happens when props or methods are called on the HTTPRequest type. -func (r HTTPRequest) Attr(name string) (starlark.Value, error) { - switch name { - case "tls": - tls := new(starlark.Dict) - tls.SetKey(starlark.String("cipher_suite"), starlark.MakeUint(uint(r.Req.TLS.CipherSuite))) - tls.SetKey(starlark.String("did_resume"), starlark.Bool(r.Req.TLS.DidResume)) - tls.SetKey(starlark.String("handshake_complete"), starlark.Bool(r.Req.TLS.HandshakeComplete)) - tls.SetKey(starlark.String("negotiated_protocol"), starlark.String(r.Req.TLS.NegotiatedProtocol)) - tls.SetKey(starlark.String("negotiated_protocol_is_mutual"), starlark.Bool(r.Req.TLS.NegotiatedProtocolIsMutual)) - tls.SetKey(starlark.String("server_name"), starlark.String(r.Req.TLS.ServerName)) - tls.SetKey(starlark.String("version"), starlark.String(r.Req.TLS.Version)) - - return tls, nil - case "header": - b := starlark.NewBuiltin("Header", r.Header) - b = b.BindReceiver(r) - - return b, nil - case "query": - qVals := r.Req.URL.Query() - query := starlark.NewDict(len(qVals)) - - for k, v := range qVals { - query.SetKey(starlark.String(k), starlark.String(v[0])) - } - - return query, nil - case "url": - return starlark.String(r.Req.URL.Path), nil - case "method": - return starlark.String(r.Req.Method), nil - case "host": - return starlark.String(r.Req.Host), nil - } - - return nil, nil -} diff --git a/internal/caddyscript/lib/lib.go b/internal/caddyscript/lib/lib.go deleted file mode 100644 index 7853e7a..0000000 --- a/internal/caddyscript/lib/lib.go +++ /dev/null @@ -1,11 +0,0 @@ -package caddyscript - -import ( - "fmt" - - "go.starlark.net/starlark" -) - -func invalidReciever(v starlark.Value, want string) (starlark.Value, error) { - return starlark.None, fmt.Errorf("invalid receiver: receiver set to type %v, want %v", v.Type(), want) -} diff --git a/internal/caddyscript/lib/regex.go b/internal/caddyscript/lib/regex.go deleted file mode 100644 index b151e64..0000000 --- a/internal/caddyscript/lib/regex.go +++ /dev/null @@ -1,50 +0,0 @@ -package caddyscript - -import ( - "fmt" - "regexp" - - "go.starlark.net/starlark" -) - -// Regexp represents a regexp type for caddyscript. -type Regexp struct{} - -// AttrNames defines what properties and methods are available on the Time type. -func (r Regexp) AttrNames() []string { - return []string{"match_string"} -} - -// Attr defines what happens when props or methods are called on the Time type. -func (r Regexp) Attr(name string) (starlark.Value, error) { - switch name { - case "match_string": - b := starlark.NewBuiltin("match_string", r.MatchString) - b = b.BindReceiver(r) - return b, nil - } - - return nil, nil -} - -// MatchString reports whether the string s contains any match of the regular expression pattern. More complicated queries need to use Compile and the full Regexp interface. -func (r Regexp) MatchString(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - var pattern, match string - err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &pattern, &match) - if err != nil { - return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error()) - } - - matched, err := regexp.MatchString(pattern, match) - if err != nil { - return starlark.False, fmt.Errorf("matchstring: %v", err.Error()) - } - - return starlark.Bool(matched), nil -} - -func (r Regexp) Freeze() {} -func (r Regexp) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: Regexp") } -func (r Regexp) String() string { return "Regexp" } -func (r Regexp) Type() string { return "Regexp" } -func (r Regexp) Truth() starlark.Bool { return true } diff --git a/internal/caddyscript/lib/time.go b/internal/caddyscript/lib/time.go deleted file mode 100644 index 2ab57cc..0000000 --- a/internal/caddyscript/lib/time.go +++ /dev/null @@ -1,130 +0,0 @@ -package caddyscript - -import ( - "fmt" - ti "time" - - "go.starlark.net/starlark" -) - -// Time represents a time type for caddyscript. -type Time struct { - value int64 // time since epoch in nanoseconds -} - -// AttrNames defines what properties and methods are available on the Time type. -func (r Time) AttrNames() []string { - return []string{"now", "parse", "add", "subtract", "minute", "hour", "day", "value"} -} - -// Attr defines what happens when props or methods are called on the Time type. -func (r Time) Attr(name string) (starlark.Value, error) { - switch name { - case "now": - b := starlark.NewBuiltin("now", r.Now) - b = b.BindReceiver(r) - return b, nil - case "parse_duration": - b := starlark.NewBuiltin("parse_duration", r.ParseDuration) - b = b.BindReceiver(r) - return b, nil - case "add": - b := starlark.NewBuiltin("add", r.Add) - b = b.BindReceiver(r) - return b, nil - case "subtract": - b := starlark.NewBuiltin("subtract", r.Subtract) - b = b.BindReceiver(r) - return b, nil - case "minute": - b := starlark.NewBuiltin("minute", r.Minute) - b = b.BindReceiver(r) - return b, nil - case "hour": - b := starlark.NewBuiltin("hour", r.Hour) - b = b.BindReceiver(r) - return b, nil - case "day": - b := starlark.NewBuiltin("day", r.Day) - b = b.BindReceiver(r) - return b, nil - case "value": - return starlark.MakeInt64(r.value), nil - } - - return nil, nil -} - -func (r Time) Freeze() {} -func (r Time) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: Time") } -func (r Time) String() string { return fmt.Sprint(r.value) } -func (r Time) Type() string { return "Time" } -func (r Time) Truth() starlark.Bool { return true } - -// Hour returns the current hour of a unix timestamp in range [0, 23]. -func (r Time) Hour(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - t := ti.Unix(0, r.value) - return starlark.MakeInt(t.Hour()), nil -} - -// Minute returns the current minute of the hour for a unix timestamp in range [0, 59]. -func (r Time) Minute(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - t := ti.Unix(0, r.value) - return starlark.MakeInt(t.Minute()), nil -} - -// Day returns the current day in a week of a unix timestamp... [Sunday = 0...] -func (r Time) Day(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - t := ti.Unix(0, r.value) - return starlark.MakeInt(int(t.Weekday())), nil -} - -// Now returns the current time as a unix timestamp. -func (r Time) Now(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - val := ti.Now().UnixNano() - r.value = val - return r, nil -} - -// ParseDuration parses a go duration string to a time type. -func (r Time) ParseDuration(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - var dur string - err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &dur) - if err != nil { - return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error()) - } - - if parsed, err := ti.ParseDuration(dur); err == nil { - val := parsed.Nanoseconds() - r.value = val - return r, nil - } - - return starlark.None, fmt.Errorf("time.parse_duration: argument cannot be parsed as a valid go time duration") -} - -// Add adds time to a time type. -func (r Time) Add(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - var t Time - err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &t) - if err != nil { - return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error()) - } - - val := r.value + t.value - r.value = val - return r, nil -} - -// Subtract adds time to a time type. -func (r Time) Subtract(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - var t Time - err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &t) - if err != nil { - return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error()) - } - - val := r.value - t.value - r.value = val - return r, nil -} diff --git a/internal/caddyscript/matcherenv.go b/internal/caddyscript/matcherenv.go deleted file mode 100644 index c6c8c0e..0000000 --- a/internal/caddyscript/matcherenv.go +++ /dev/null @@ -1,18 +0,0 @@ -package caddyscript - -import ( - "net/http" - - caddyscript "bitbucket.org/lightcodelabs/caddy2/internal/caddyscript/lib" - "go.starlark.net/starlark" -) - -// MatcherEnv sets up the global context for the matcher caddyscript environment. -func MatcherEnv(r *http.Request) starlark.StringDict { - env := make(starlark.StringDict) - env["req"] = caddyscript.HTTPRequest{Req: r} - env["time"] = caddyscript.Time{} - env["regexp"] = caddyscript.Regexp{} - - return env -} diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go index 449d07f..379c8f2 100644 --- a/modules/caddyhttp/caddyhttp.go +++ b/modules/caddyhttp/caddyhttp.go @@ -24,6 +24,7 @@ func init() { Name: "http", New: func() (interface{}, error) { return new(App), nil }, }) + if err != nil { log.Fatal(err) } @@ -94,11 +95,21 @@ func (app *App) Validate() error { return nil } +// AutomaticHTTPSError represents an error received when attempting to enable automatic https. +type AutomaticHTTPSError struct { + internal error +} + +// Error returns the error string for automaticHTTPSError. +func (a AutomaticHTTPSError) Error() string { + return fmt.Sprintf("enabling automatic HTTPS: %v", a.internal.Error()) +} + // Start runs the app. It sets up automatic HTTPS if enabled. func (app *App) Start() error { err := app.automaticHTTPS() if err != nil { - return fmt.Errorf("enabling automatic HTTPS: %v", err) + return &AutomaticHTTPSError{internal: err} } for srvName, srv := range app.Servers { diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index e467c84..5eb0837 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -12,7 +12,7 @@ import ( "strings" "bitbucket.org/lightcodelabs/caddy2" - "bitbucket.org/lightcodelabs/caddy2/internal/caddyscript" + "bitbucket.org/lightcodelabs/caddy2/pkg/caddyscript" "go.starlark.net/starlark" ) diff --git a/modules/caddyhttp/reverseproxy/upstream.go b/modules/caddyhttp/reverseproxy/upstream.go index 7e429f9..1217861 100755 --- a/modules/caddyhttp/reverseproxy/upstream.go +++ b/modules/caddyhttp/reverseproxy/upstream.go @@ -17,12 +17,6 @@ import ( "bitbucket.org/lightcodelabs/caddy2" ) -// State represents the global state of a loadbalancer. It is used to store -// references to health checkers. -type State struct { - HealthCheckers []*HealthChecker -} - // CircuitBreaker defines the functionality of a circuit breaker module. type CircuitBreaker interface { Ok() bool @@ -76,7 +70,7 @@ var ( ) // NewLoadBalancedReverseProxy returns a collection of Upstreams that are to be loadbalanced. -func NewLoadBalancedReverseProxy(lb *LoadBalanced, state *State, ctx caddy2.Context) error { +func NewLoadBalancedReverseProxy(lb *LoadBalanced, ctx caddy2.Context) error { // set defaults if lb.NoHealthyUpstreamsMessage == "" { lb.NoHealthyUpstreamsMessage = msgNoHealthyUpstreams @@ -145,7 +139,7 @@ func NewLoadBalancedReverseProxy(lb *LoadBalanced, state *State, ctx caddy2.Cont // nu.Target.Path = uc.HealthCheckPath go nu.healthChecker.ScheduleChecks(nu.Target.String()) - state.HealthCheckers = append(state.HealthCheckers, nu.healthChecker) + lb.HealthCheckers = append(lb.HealthCheckers, nu.healthChecker) us = append(us, nu) } @@ -178,6 +172,25 @@ type LoadBalanced struct { // NoHealthyUpstreamsMessage is returned as a response when there are no healthy upstreams to loadbalance to. NoHealthyUpstreamsMessage string `json:"no_healthy_upstreams_message"` + + // TODO :- store healthcheckers as package level state where each upstream gets a single healthchecker + // currently a healthchecker is created for each upstream defined, even if a healthchecker was previously created + // for that upstream + HealthCheckers []*HealthChecker +} + +// Cleanup stops all health checkers on a loadbalanced reverse proxy. +func (lb *LoadBalanced) Cleanup() error { + for _, hc := range lb.HealthCheckers { + hc.Stop() + } + + return nil +} + +// Provision sets up a new loadbalanced reverse proxy. +func (lb *LoadBalanced) Provision(ctx caddy2.Context) error { + return NewLoadBalancedReverseProxy(lb, ctx) } // ServeHTTP implements the http.Handler interface to dispatch an http request to the proper diff --git a/pkg/caddyscript/lib/http.go b/pkg/caddyscript/lib/http.go new file mode 100644 index 0000000..0e42458 --- /dev/null +++ b/pkg/caddyscript/lib/http.go @@ -0,0 +1,97 @@ +package caddyscript + +import ( + "fmt" + "net/http" + + "github.com/starlight-go/starlight/convert" + "go.starlark.net/starlark" +) + +// HTTPRequest represents an http request type in caddyscript. +type HTTPRequest struct{ Req *http.Request } + +// AttrNames defines what properties and methods are available on the HTTPRequest type. +func (r HTTPRequest) AttrNames() []string { + return []string{"header", "query", "url", "method", "host", "tls", "redirect"} +} + +func (r HTTPRequest) Freeze() {} +func (r HTTPRequest) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: HTTPRequest") } +func (r HTTPRequest) String() string { return fmt.Sprint(r.Req) } +func (r HTTPRequest) Type() string { return "HTTPRequest" } +func (r HTTPRequest) Truth() starlark.Bool { return true } + +// Header handles returning a header key. +func (r HTTPRequest) Header(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var key string + err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &key) + if err != nil { + return starlark.None, fmt.Errorf("get request header: %v", err.Error()) + } + + return starlark.String(r.Req.Header.Get(key)), nil +} + +// Redirect handles an http redirect from starlark code. +func (r HTTPRequest) Redirect(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var w starlark.Value + var req HTTPRequest + var newURL string + err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 3, &w, &req, &newURL) + if err != nil { + return starlark.None, fmt.Errorf("unpacking arguments: %v", err.Error()) + } + + writer := convert.FromValue(w) + if w, ok := writer.(http.ResponseWriter); ok { + http.Redirect(w, req.Req, newURL, http.StatusSeeOther) + return starlark.None, nil + } + + return starlark.None, fmt.Errorf("first provided argument is not http.ResponseWriter") +} + +// Attr defines what happens when props or methods are called on the HTTPRequest type. +func (r HTTPRequest) Attr(name string) (starlark.Value, error) { + switch name { + case "redirect": + b := starlark.NewBuiltin("Redirect", r.Redirect) + b = b.BindReceiver(r) + + return b, nil + case "tls": + tls := new(starlark.Dict) + tls.SetKey(starlark.String("cipher_suite"), starlark.MakeUint(uint(r.Req.TLS.CipherSuite))) + tls.SetKey(starlark.String("did_resume"), starlark.Bool(r.Req.TLS.DidResume)) + tls.SetKey(starlark.String("handshake_complete"), starlark.Bool(r.Req.TLS.HandshakeComplete)) + tls.SetKey(starlark.String("negotiated_protocol"), starlark.String(r.Req.TLS.NegotiatedProtocol)) + tls.SetKey(starlark.String("negotiated_protocol_is_mutual"), starlark.Bool(r.Req.TLS.NegotiatedProtocolIsMutual)) + tls.SetKey(starlark.String("server_name"), starlark.String(r.Req.TLS.ServerName)) + tls.SetKey(starlark.String("version"), starlark.String(r.Req.TLS.Version)) + + return tls, nil + case "header": + b := starlark.NewBuiltin("Header", r.Header) + b = b.BindReceiver(r) + + return b, nil + case "query": + qVals := r.Req.URL.Query() + query := starlark.NewDict(len(qVals)) + + for k, v := range qVals { + query.SetKey(starlark.String(k), starlark.String(v[0])) + } + + return query, nil + case "url": + return starlark.String(r.Req.URL.Path), nil + case "method": + return starlark.String(r.Req.Method), nil + case "host": + return starlark.String(r.Req.Host), nil + } + + return nil, nil +} diff --git a/pkg/caddyscript/lib/regex.go b/pkg/caddyscript/lib/regex.go new file mode 100644 index 0000000..b151e64 --- /dev/null +++ b/pkg/caddyscript/lib/regex.go @@ -0,0 +1,50 @@ +package caddyscript + +import ( + "fmt" + "regexp" + + "go.starlark.net/starlark" +) + +// Regexp represents a regexp type for caddyscript. +type Regexp struct{} + +// AttrNames defines what properties and methods are available on the Time type. +func (r Regexp) AttrNames() []string { + return []string{"match_string"} +} + +// Attr defines what happens when props or methods are called on the Time type. +func (r Regexp) Attr(name string) (starlark.Value, error) { + switch name { + case "match_string": + b := starlark.NewBuiltin("match_string", r.MatchString) + b = b.BindReceiver(r) + return b, nil + } + + return nil, nil +} + +// MatchString reports whether the string s contains any match of the regular expression pattern. More complicated queries need to use Compile and the full Regexp interface. +func (r Regexp) MatchString(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var pattern, match string + err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 2, &pattern, &match) + if err != nil { + return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error()) + } + + matched, err := regexp.MatchString(pattern, match) + if err != nil { + return starlark.False, fmt.Errorf("matchstring: %v", err.Error()) + } + + return starlark.Bool(matched), nil +} + +func (r Regexp) Freeze() {} +func (r Regexp) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: Regexp") } +func (r Regexp) String() string { return "Regexp" } +func (r Regexp) Type() string { return "Regexp" } +func (r Regexp) Truth() starlark.Bool { return true } diff --git a/pkg/caddyscript/lib/time.go b/pkg/caddyscript/lib/time.go new file mode 100644 index 0000000..2ab57cc --- /dev/null +++ b/pkg/caddyscript/lib/time.go @@ -0,0 +1,130 @@ +package caddyscript + +import ( + "fmt" + ti "time" + + "go.starlark.net/starlark" +) + +// Time represents a time type for caddyscript. +type Time struct { + value int64 // time since epoch in nanoseconds +} + +// AttrNames defines what properties and methods are available on the Time type. +func (r Time) AttrNames() []string { + return []string{"now", "parse", "add", "subtract", "minute", "hour", "day", "value"} +} + +// Attr defines what happens when props or methods are called on the Time type. +func (r Time) Attr(name string) (starlark.Value, error) { + switch name { + case "now": + b := starlark.NewBuiltin("now", r.Now) + b = b.BindReceiver(r) + return b, nil + case "parse_duration": + b := starlark.NewBuiltin("parse_duration", r.ParseDuration) + b = b.BindReceiver(r) + return b, nil + case "add": + b := starlark.NewBuiltin("add", r.Add) + b = b.BindReceiver(r) + return b, nil + case "subtract": + b := starlark.NewBuiltin("subtract", r.Subtract) + b = b.BindReceiver(r) + return b, nil + case "minute": + b := starlark.NewBuiltin("minute", r.Minute) + b = b.BindReceiver(r) + return b, nil + case "hour": + b := starlark.NewBuiltin("hour", r.Hour) + b = b.BindReceiver(r) + return b, nil + case "day": + b := starlark.NewBuiltin("day", r.Day) + b = b.BindReceiver(r) + return b, nil + case "value": + return starlark.MakeInt64(r.value), nil + } + + return nil, nil +} + +func (r Time) Freeze() {} +func (r Time) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: Time") } +func (r Time) String() string { return fmt.Sprint(r.value) } +func (r Time) Type() string { return "Time" } +func (r Time) Truth() starlark.Bool { return true } + +// Hour returns the current hour of a unix timestamp in range [0, 23]. +func (r Time) Hour(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + t := ti.Unix(0, r.value) + return starlark.MakeInt(t.Hour()), nil +} + +// Minute returns the current minute of the hour for a unix timestamp in range [0, 59]. +func (r Time) Minute(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + t := ti.Unix(0, r.value) + return starlark.MakeInt(t.Minute()), nil +} + +// Day returns the current day in a week of a unix timestamp... [Sunday = 0...] +func (r Time) Day(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + t := ti.Unix(0, r.value) + return starlark.MakeInt(int(t.Weekday())), nil +} + +// Now returns the current time as a unix timestamp. +func (r Time) Now(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + val := ti.Now().UnixNano() + r.value = val + return r, nil +} + +// ParseDuration parses a go duration string to a time type. +func (r Time) ParseDuration(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var dur string + err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &dur) + if err != nil { + return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error()) + } + + if parsed, err := ti.ParseDuration(dur); err == nil { + val := parsed.Nanoseconds() + r.value = val + return r, nil + } + + return starlark.None, fmt.Errorf("time.parse_duration: argument cannot be parsed as a valid go time duration") +} + +// Add adds time to a time type. +func (r Time) Add(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var t Time + err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &t) + if err != nil { + return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error()) + } + + val := r.value + t.value + r.value = val + return r, nil +} + +// Subtract adds time to a time type. +func (r Time) Subtract(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var t Time + err := starlark.UnpackPositionalArgs(fn.Name(), args, kwargs, 1, &t) + if err != nil { + return starlark.None, fmt.Errorf("could not unpack args: %v", err.Error()) + } + + val := r.value - t.value + r.value = val + return r, nil +} diff --git a/pkg/caddyscript/matcherenv.go b/pkg/caddyscript/matcherenv.go new file mode 100644 index 0000000..aa9d7fd --- /dev/null +++ b/pkg/caddyscript/matcherenv.go @@ -0,0 +1,18 @@ +package caddyscript + +import ( + "net/http" + + caddyscript "bitbucket.org/lightcodelabs/caddy2/pkg/caddyscript/lib" + "go.starlark.net/starlark" +) + +// MatcherEnv sets up the global context for the matcher caddyscript environment. +func MatcherEnv(r *http.Request) starlark.StringDict { + env := make(starlark.StringDict) + env["req"] = caddyscript.HTTPRequest{Req: r} + env["time"] = caddyscript.Time{} + env["regexp"] = caddyscript.Regexp{} + + return env +} -- cgit v1.2.3