diff --git a/.goreleaser.yaml b/.goreleaser.yaml index e6b0bcb..fab9a1c 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -60,6 +60,9 @@ nfpms: - src: misc/packaging/common/config.yml dst: /etc/bouncer/config.yml type: config + - src: layers + dst: /etc/bouncer/layers + type: config - id: bouncer-admin meta: true package_name: bouncer-admin diff --git a/go.mod b/go.mod index 6217e6b..0464115 100644 --- a/go.mod +++ b/go.mod @@ -4,17 +4,21 @@ go 1.20 require ( forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230512083245-e2dc3e1a0333 + github.com/Masterminds/sprig/v3 v3.2.3 github.com/btcsuite/btcd/btcutil v1.1.3 - github.com/davecgh/go-spew v1.1.1 github.com/go-chi/chi/v5 v5.0.8 github.com/jedib0t/go-pretty/v6 v6.4.6 + github.com/mitchellh/mapstructure v1.4.1 github.com/ory/dockertest/v3 v3.10.0 + github.com/qri-io/jsonschema v0.2.1 github.com/redis/go-redis/v9 v9.0.4 ) require ( cloud.google.com/go v0.99.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect @@ -28,19 +32,22 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.1.5 // indirect github.com/qri-io/jsonpointer v0.1.1 // indirect - github.com/qri-io/jsonschema v0.2.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/cast v1.3.1 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect diff --git a/go.sum b/go.sum index 4b7f089..3184cf7 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= @@ -282,8 +288,11 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= @@ -338,9 +347,13 @@ github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp9 github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= @@ -391,11 +404,16 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -456,6 +474,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= @@ -539,6 +558,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= @@ -640,12 +660,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= @@ -659,6 +681,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= diff --git a/internal/command/server/proxy/run.go b/internal/command/server/proxy/run.go index 82ad72b..3806952 100644 --- a/internal/command/server/proxy/run.go +++ b/internal/command/server/proxy/run.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "time" "forge.cadoles.com/cadoles/bouncer/internal/command/common" "forge.cadoles.com/cadoles/bouncer/internal/config" @@ -83,5 +84,16 @@ func initQueueLayer(ctx context.Context, conf *config.Config) (*queue.Queue, err return nil, errors.WithStack(err) } - return queue.New(adapter), nil + options := []queue.OptionFunc{ + queue.WithTemplateDir(string(conf.Layers.Queue.TemplateDir)), + } + + if conf.Layers.Queue.DefaultKeepAlive != nil { + options = append(options, queue.WithDefaultKeepAlive(time.Duration(*conf.Layers.Queue.DefaultKeepAlive))) + } + + return queue.New( + adapter, + options..., + ), nil } diff --git a/internal/config/config.go b/internal/config/config.go index 2a5d087..8af5651 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,6 +14,7 @@ type Config struct { Proxy ProxyServerConfig `yaml:"proxy"` Redis RedisConfig `yaml:"redis"` Logger LoggerConfig `yaml:"logger"` + Layers LayersConfig `yaml:"layers"` } // NewFromFile retrieves the configuration from the given file @@ -46,6 +47,7 @@ func NewDefault() *Config { Proxy: NewDefaultProxyServerConfig(), Logger: NewDefaultLoggerConfig(), Redis: NewDefaultRedisConfig(), + Layers: NewDefaultLayersConfig(), } } diff --git a/internal/config/environment.go b/internal/config/environment.go index 3b1b3d2..82470d8 100644 --- a/internal/config/environment.go +++ b/internal/config/environment.go @@ -4,6 +4,7 @@ import ( "os" "regexp" "strconv" + "time" "github.com/pkg/errors" "gopkg.in/yaml.v3" @@ -123,3 +124,37 @@ func (iss *InterpolatedStringSlice) UnmarshalYAML(value *yaml.Node) error { return nil } + +type InterpolatedDuration time.Duration + +func (id *InterpolatedDuration) UnmarshalYAML(value *yaml.Node) error { + var str string + + if err := value.Decode(&str); err != nil { + return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line) + } + + if match := reVar.FindStringSubmatch(str); len(match) > 0 { + str = os.Getenv(match[1]) + } + + duration, err := time.ParseDuration(str) + if err != nil { + return errors.Wrapf(err, "could not parse duration '%v', line '%d'", str, value.Line) + } + + *id = InterpolatedDuration(duration) + + return nil +} + +func (id *InterpolatedDuration) MarshalYAML() (interface{}, error) { + duration := time.Duration(*id) + + return duration.String(), nil +} + +func NewInterpolatedDuration(d time.Duration) *InterpolatedDuration { + id := InterpolatedDuration(d) + return &id +} diff --git a/internal/config/layers.go b/internal/config/layers.go new file mode 100644 index 0000000..b9e6630 --- /dev/null +++ b/internal/config/layers.go @@ -0,0 +1,21 @@ +package config + +import "time" + +type LayersConfig struct { + Queue QueueLayerConfig `yaml:"queue"` +} + +func NewDefaultLayersConfig() LayersConfig { + return LayersConfig{ + Queue: QueueLayerConfig{ + TemplateDir: "./layers/queue/templates", + DefaultKeepAlive: NewInterpolatedDuration(time.Minute), + }, + } +} + +type QueueLayerConfig struct { + TemplateDir InterpolatedString `yaml:"templateDir"` + DefaultKeepAlive *InterpolatedDuration `yaml:"defaultKeepAlive"` +} diff --git a/internal/queue/layer_options.go b/internal/queue/layer_options.go index 5cc9d9e..7925a1b 100644 --- a/internal/queue/layer_options.go +++ b/internal/queue/layer_options.go @@ -15,11 +15,11 @@ type LayerOptions struct { KeepAlive time.Duration `mapstructure:"keepAlive"` } -func fromStoreOptions(storeOptions store.LayerOptions) (*LayerOptions, error) { +func fromStoreOptions(storeOptions store.LayerOptions, defaultKeepAlive time.Duration) (*LayerOptions, error) { layerOptions := LayerOptions{ Capacity: 1000, Matchers: []string{"*"}, - KeepAlive: 30 * time.Second, + KeepAlive: defaultKeepAlive, } config := mapstructure.DecoderConfig{ diff --git a/internal/queue/options.go b/internal/queue/options.go index 80426cb..db4a6ad 100644 --- a/internal/queue/options.go +++ b/internal/queue/options.go @@ -1,9 +1,29 @@ package queue -type Options struct{} +import "time" + +type Options struct { + TemplateDir string + DefaultKeepAlive time.Duration +} type OptionFunc func(*Options) func defaultOptions() *Options { - return &Options{} + return &Options{ + TemplateDir: "./templates", + DefaultKeepAlive: time.Minute, + } +} + +func WithTemplateDir(templateDir string) OptionFunc { + return func(o *Options) { + o.TemplateDir = templateDir + } +} + +func WithDefaultKeepAlive(keepAlive time.Duration) OptionFunc { + return func(o *Options) { + o.DefaultKeepAlive = keepAlive + } } diff --git a/internal/queue/queue.go b/internal/queue/queue.go index 47e1c4c..652d5a1 100644 --- a/internal/queue/queue.go +++ b/internal/queue/queue.go @@ -3,13 +3,17 @@ package queue import ( "context" "fmt" + "html/template" "net/http" + "path/filepath" + "sync" "sync/atomic" "time" "forge.cadoles.com/Cadoles/go-proxy" "forge.cadoles.com/cadoles/bouncer/internal/proxy/director" "forge.cadoles.com/cadoles/bouncer/internal/store" + "github.com/Masterminds/sprig/v3" "github.com/google/uuid" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" @@ -18,7 +22,14 @@ import ( const LayerType store.LayerType = "queue" type Queue struct { - adapter Adapter + adapter Adapter + + defaultKeepAlive time.Duration + + templateDir string + loadOnce sync.Once + tmpl *template.Template + refreshJobRunning uint32 } @@ -33,7 +44,7 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware { fn := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - options, err := fromStoreOptions(layer.Options) + options, err := fromStoreOptions(layer.Options, q.defaultKeepAlive) if err != nil { logger.Error(ctx, "could not parse layer options", logger.E(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) @@ -102,7 +113,52 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam return } - http.Error(w, fmt.Sprintf("queued (rank: %d, status: %d/%d)", rank+1, status.Sessions, options.Capacity), http.StatusServiceUnavailable) + q.loadOnce.Do(func() { + pattern := filepath.Join(q.templateDir, "*.gohtml") + + logger.Info(ctx, "loading queue page templates", logger.F("pattern", pattern)) + + tmpl, err := template.New("").Funcs(sprig.FuncMap()).ParseGlob(pattern) + if err != nil { + logger.Error(ctx, "could not load queue templates", logger.E(errors.WithStack(err))) + + return + } + + q.tmpl = tmpl + }) + + if q.tmpl == nil { + logger.Error(ctx, "queue page templates not loaded", logger.E(errors.WithStack(err))) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + + return + } + + templateData := struct { + QueueName string + LayerOptions *LayerOptions + Rank int64 + CurrentSessions int64 + MaxSessions int64 + RefreshRate int64 + }{ + QueueName: queueName, + LayerOptions: options, + Rank: rank + 1, + CurrentSessions: status.Sessions, + MaxSessions: options.Capacity, + RefreshRate: 5, + } + + w.WriteHeader(http.StatusServiceUnavailable) + + if err := q.tmpl.ExecuteTemplate(w, "queue", templateData); err != nil { + logger.Error(ctx, "could not render queue page", logger.E(errors.WithStack(err))) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + + return + } } func (q *Queue) refreshQueue(queueName string, keepAlive time.Duration) { @@ -136,7 +192,9 @@ func New(adapter Adapter, funcs ...OptionFunc) *Queue { } return &Queue{ - adapter: adapter, + adapter: adapter, + templateDir: opts.TemplateDir, + defaultKeepAlive: opts.DefaultKeepAlive, } } diff --git a/internal/setup/queue_repository.go b/internal/setup/queue_repository.go index 51a73b9..a31c139 100644 --- a/internal/setup/queue_repository.go +++ b/internal/setup/queue_repository.go @@ -10,10 +10,10 @@ import ( queueRedis "forge.cadoles.com/cadoles/bouncer/internal/queue/redis" ) -func NewQueueAdapter(ctx context.Context, conf config.RedisConfig) (queue.Adapter, error) { +func NewQueueAdapter(ctx context.Context, redisConf config.RedisConfig) (queue.Adapter, error) { rdb := redis.NewUniversalClient(&redis.UniversalOptions{ - Addrs: conf.Adresses, - MasterName: string(conf.Master), + Addrs: redisConf.Adresses, + MasterName: string(redisConf.Master), }) return queueRedis.NewAdapter(rdb, 2), nil diff --git a/layers/queue/templates/queue.gohtml b/layers/queue/templates/queue.gohtml new file mode 100644 index 0000000..af6cb5c --- /dev/null +++ b/layers/queue/templates/queue.gohtml @@ -0,0 +1,84 @@ +{{ define "queue" }} + + + + + + Veuillez patienter - {{ .QueueName }} + + + + +
+
+

Un instant s'il vous plaît...

+

Le site auquel vous souhaitez accéder est actuellement surchargé.

+ {{ $rank := sub .Rank .MaxSessions }} + {{ $waiting := sub .CurrentSessions .MaxSessions }} +

+ Position: {{ $rank }} (sur {{ $waiting }} en attente) +

+

Cette page se rafraîchira automatiquement lorsqu'une place sera disponible.

+ +
+
+ + +{{ end }} \ No newline at end of file diff --git a/misc/packaging/common/config.yml b/misc/packaging/common/config.yml index f178ed7..685bd93 100644 --- a/misc/packaging/common/config.yml +++ b/misc/packaging/common/config.yml @@ -61,3 +61,12 @@ logger: level: 1 # Format des logs, "human" ou "json" format: human + +# Configuration des différents layers +layers: + # Configuration du layer "queue" + queue: + # Répertoire contenant les templates + templateDir: "/etc/bouncer/layers/queue/templates" + # Temps de vie par défaut d'une session + defaultKeepAlive: 1m \ No newline at end of file diff --git a/modd.conf b/modd.conf index 04bebf0..7df0cba 100644 --- a/modd.conf +++ b/modd.conf @@ -2,6 +2,7 @@ internal/**/*.json modd.conf config.yml +layers/** .env { prep: make RUN_INSTALL_TESTS=no GOTEST_ARGS="-short" test prep: make build-bouncer