mirror of
https://git.vectorsigma.ru/public/k3s.git
synced 2026-08-03 18:50:00 +00:00
Merge pull request #1045 from ibuildthecloud/dqlite4
Add DQLite support but disabled by the build
This commit is contained in:
14
go.mod
14
go.mod
@@ -64,6 +64,7 @@ require (
|
||||
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e // indirect
|
||||
github.com/bronze1man/goStrongswanVici v0.0.0-20190828090544-27d02f80ba40 // indirect
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect
|
||||
github.com/canonical/go-dqlite v1.1.0
|
||||
github.com/containerd/cgroups v0.0.0-20190923161937-abd0b19954a6 // indirect
|
||||
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69
|
||||
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect
|
||||
@@ -75,30 +76,32 @@ require (
|
||||
github.com/containernetworking/plugins v0.8.2 // indirect
|
||||
github.com/coreos/flannel v0.11.0
|
||||
github.com/coreos/go-iptables v0.4.2
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
|
||||
github.com/docker/docker v0.7.3-0.20190731001754-589f1dad8dad
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/libnetwork v0.8.0-dev.2.0.20190624125649-f0e46a78ea34 // indirect
|
||||
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 // indirect
|
||||
github.com/go-bindata/go-bindata v3.1.2+incompatible
|
||||
github.com/go-sql-driver/mysql v1.4.1
|
||||
github.com/gofrs/flock v0.7.1 // indirect
|
||||
github.com/gogo/googleapis v1.3.0 // indirect
|
||||
github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d // indirect
|
||||
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect
|
||||
github.com/kubernetes-sigs/cri-tools v0.0.0-00010101000000-000000000000
|
||||
github.com/lib/pq v1.1.1
|
||||
github.com/lxc/lxd v0.0.0-20191108214106-60ea15630455
|
||||
github.com/mattn/go-sqlite3 v1.10.0
|
||||
github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989 // indirect
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||
github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/rakelkar/gonetsh v0.0.0-20190719023240-501daadcadf8 // indirect
|
||||
github.com/rancher/dynamiclistener v0.1.1-0.20191108205817-245f86cc340a
|
||||
github.com/rancher/dynamiclistener v0.1.1-0.20191110035254-aaa5bc0d2a07
|
||||
github.com/rancher/helm-controller v0.2.2
|
||||
github.com/rancher/kine v0.1.2-0.20191107225357-527576e3452f
|
||||
github.com/rancher/kine v0.2.1
|
||||
github.com/rancher/remotedialer v0.2.0
|
||||
github.com/rancher/wrangler v0.2.0
|
||||
github.com/rancher/wrangler-api v0.2.0
|
||||
@@ -108,11 +111,12 @@ require (
|
||||
github.com/tchap/go-patricia v2.3.0+incompatible // indirect
|
||||
github.com/theckman/go-flock v0.7.1 // indirect
|
||||
github.com/urfave/cli v1.21.0
|
||||
go.etcd.io/bbolt v1.3.3 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8
|
||||
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3
|
||||
google.golang.org/grpc v1.23.0
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
|
||||
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
k8s.io/api v0.0.0
|
||||
k8s.io/apimachinery v0.0.0
|
||||
|
||||
59
go.sum
59
go.sum
@@ -80,6 +80,8 @@ github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E=
|
||||
github.com/canonical/go-dqlite v1.1.0 h1:vXGVhHrql++q038JVZk17/VZDbvbH5ySWcObWjuxiBQ=
|
||||
github.com/canonical/go-dqlite v1.1.0/go.mod h1:wp00vfMvPYgNCyxcPdHB5XExmDoCGoPUGymloAQT17Y=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY=
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8=
|
||||
@@ -119,6 +121,8 @@ github.com/containernetworking/plugins v0.8.2/go.mod h1:TxALKWZpWL79BC3GOYKJzzXr
|
||||
github.com/coredns/corefile-migration v1.0.2/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E=
|
||||
github.com/coreos/bbolt v1.3.1-coreos.6 h1:uTXKg9gY70s9jMAKdfljFQcuh4e/BXOM+V+d00KFj3A=
|
||||
github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.15+incompatible h1:+9RjdC18gMxNQVvSiXvObLu29mOFmkgdsB4cRTlV+EE=
|
||||
@@ -135,6 +139,8 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5t
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea h1:n2Ltr3SrfQlf/9nOna1DoGKxLx3qTSI8Ttl6Xrqp6mw=
|
||||
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/rkt v1.30.0 h1:Kkt6sYeEGKxA3Y7SCrY+nHoXkWed6Jr2BBY42GqMymM=
|
||||
github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
@@ -187,6 +193,8 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM
|
||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 h1:GY1+t5Dr9OKADM64SYnQjw/w99HMYvQ0A8/JoUkxVmc=
|
||||
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@@ -201,6 +209,8 @@ github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG
|
||||
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE=
|
||||
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
@@ -318,6 +328,8 @@ github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e h1:KhcknUwkWHKZ
|
||||
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/cadvisor v0.34.0 h1:No7G6U/TasplR9uNqyc5Jj0Bet5VSYsK5xLygOf4pUw=
|
||||
github.com/google/cadvisor v0.34.0/go.mod h1:1nql6U13uTHaLYB8rLS5x9IJc2qT6Xd/Tr1sTX6NE48=
|
||||
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
|
||||
@@ -351,15 +363,21 @@ github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w=
|
||||
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79 h1:lR9ssWAqp9qL0bALxqEEkuudiP1eweOdv9jsRK3e7lE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.3.0 h1:HJtP6RRwj2EpPCD/mhAWzSvLL/dFTdPm1UrWwanoFos=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.11.2 h1:bUDfHRK8aKGdya+msYJHffDwNxB8Eileyl7Jf2qqYjI=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.11.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
@@ -375,8 +393,6 @@ github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7U
|
||||
github.com/heketi/utils v0.0.0-20170317161834-435bc5bdfa64/go.mod h1:RYlF4ghFZPPmk2TC5REt5OFwvfb6lzxFWrTWB+qs28s=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ibuildthecloud/kine v0.1.1 h1:HAqWHrjRDoqQ3+pKuJrk7Sx9nzoW3zotzhxMfAZ4YME=
|
||||
github.com/ibuildthecloud/kine v0.1.1/go.mod h1:TTWUtUeu7dHQan9BrCtlRbKr9eK7epHqrBFOAae15Bg=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
@@ -396,10 +412,13 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc=
|
||||
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||
github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc=
|
||||
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||
@@ -438,6 +457,8 @@ github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH
|
||||
github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk=
|
||||
github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao=
|
||||
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
|
||||
github.com/lxc/lxd v0.0.0-20191108214106-60ea15630455 h1:gQQV7It0kjZxMLJkS/+5Mc6w0zM6pKGzl3OS0h2RHrY=
|
||||
github.com/lxc/lxd v0.0.0-20191108214106-60ea15630455/go.mod h1:2BaZflfwsv8a3uy3/Vw+de4Avn4DSrAiqaHJjCIXMV4=
|
||||
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -527,6 +548,7 @@ github.com/opencontainers/runtime-spec v0.0.0-20180911193056-5684b8af48c1/go.mod
|
||||
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
|
||||
github.com/opencontainers/selinux v1.2.2 h1:Kx9J6eDG5/24A6DtUquGSpJQ+m2MUTahn4FtGEe8bFg=
|
||||
github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
@@ -562,16 +584,14 @@ github.com/rancher/cri v1.3.0-k3s.2 h1:k2XFyD+ZdsGvNfugdvqD38KSMANT3JmTFULFM2CtI
|
||||
github.com/rancher/cri v1.3.0-k3s.2/go.mod h1:Ht5T1dIKzm+4NExmb7wDVG6qR+j0xeXIjjhCv1d9geY=
|
||||
github.com/rancher/cri-tools v1.16.1-k3s.1 h1:iporgQ46noE6dtLzq6fWcIO2qjyPZy2m42d2P+UnGJg=
|
||||
github.com/rancher/cri-tools v1.16.1-k3s.1/go.mod h1:TEKhKv2EJIZp+p9jnEy4C63g8CosJzsI4kyKKkHag+8=
|
||||
github.com/rancher/dynamiclistener v0.1.1-0.20191031022009-6224794ef3cb h1:bMoA9UHr1QNTWVrf0fSJCba6YDU1xmt2jmeohpiugKg=
|
||||
github.com/rancher/dynamiclistener v0.1.1-0.20191031022009-6224794ef3cb/go.mod h1:fs/dxyNcB3YT6W9fVz4bDGfhmSQS17QQup6BIcGF++s=
|
||||
github.com/rancher/dynamiclistener v0.1.1-0.20191108205817-245f86cc340a h1:yIQXTC2BjGQ4Bt5Y7QhnxNWbbq8e6koH+pFrJL2VsIs=
|
||||
github.com/rancher/dynamiclistener v0.1.1-0.20191108205817-245f86cc340a/go.mod h1:fs/dxyNcB3YT6W9fVz4bDGfhmSQS17QQup6BIcGF++s=
|
||||
github.com/rancher/dynamiclistener v0.1.1-0.20191110035254-aaa5bc0d2a07 h1:wR1hnAh7d7ZicsAwDyw2nfvGFDOvPojcfClwA8WGy5g=
|
||||
github.com/rancher/dynamiclistener v0.1.1-0.20191110035254-aaa5bc0d2a07/go.mod h1:fs/dxyNcB3YT6W9fVz4bDGfhmSQS17QQup6BIcGF++s=
|
||||
github.com/rancher/flannel v0.11.0-k3s.1 h1:mIwnfWDafjzQgFkZeJ1AkFrrAT3EdBaA1giE0eLJKo8=
|
||||
github.com/rancher/flannel v0.11.0-k3s.1/go.mod h1:Hn4ZV+eq0LhLZP63xZnxdGwXEoRSxs5sxELxu27M3UA=
|
||||
github.com/rancher/helm-controller v0.2.2 h1:MUqisy53/Ay1EYOF2uTCYBbGpgtZLNKKrI01BdxIbQo=
|
||||
github.com/rancher/helm-controller v0.2.2/go.mod h1:0JkL0UjxddNbT4FmLoESarD4Mz8xzA5YlejqJ/U4g+8=
|
||||
github.com/rancher/kine v0.1.2-0.20191107225357-527576e3452f h1:tUNKo4xpQfR2Qfg/vyI5/pdBaC730Lu/jqTEqgeo83o=
|
||||
github.com/rancher/kine v0.1.2-0.20191107225357-527576e3452f/go.mod h1:TTWUtUeu7dHQan9BrCtlRbKr9eK7epHqrBFOAae15Bg=
|
||||
github.com/rancher/kine v0.2.1 h1:pK7QJUVA+/oU6esxKa/LIlBbeLl2HGWIwmu8xrROukQ=
|
||||
github.com/rancher/kine v0.2.1/go.mod h1:SdBUuE7e3XyrJvdBxCl9TMMapF+wyZnMZSP/H59OqNE=
|
||||
github.com/rancher/kubernetes v1.16.2-k3s.1 h1:+oJEecXgQDkEOD/X8z2YUdYVonbXZtGzXsmtKDPYesg=
|
||||
github.com/rancher/kubernetes v1.16.2-k3s.1/go.mod h1:SmhGgKfQ30imqjFVj8AI+iW+zSyFsswNErKYeTfgoH0=
|
||||
github.com/rancher/kubernetes/staging/src/k8s.io/api v1.16.2-k3s.1 h1:2kK5KD6MU86txBYKG+tM6j5zbey02DaIDtwpG5JsfnI=
|
||||
@@ -629,6 +649,7 @@ github.com/rancher/wrangler-api v0.2.0/go.mod h1:zTPdNLZO07KvRaVOx6XQbKBSV55Fnn4
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/robfig/cron v1.1.0 h1:jk4/Hud3TTdcrJgUOBgsqrZBarcxl6ADIjSC2iniwLY=
|
||||
github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rootless-containers/rootlesskit v0.6.0 h1:L7DxVAlaNhg4M/+i2GCl24kRkXO5q81C/lu4jlMz3bE=
|
||||
@@ -657,6 +678,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.3 h1:09wy7WZk4AqO03yH85Ex1X+Uo3vDsil3Fa9AgF8Emss=
|
||||
github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
|
||||
github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
@@ -685,6 +708,8 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 h1:zLV6q4e8Jv9EHjNg/iHfzwDkCve6Ua5jCygptrtXHvI=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
@@ -697,6 +722,8 @@ github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLY
|
||||
github.com/timakin/bodyclose v0.0.0-20190407043127-4a873e97b2bb/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
@@ -720,6 +747,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:
|
||||
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 h1:MPPkRncZLN9Kh4MEFmbnK4h3BD7AUmskWv2+EeZJCCs=
|
||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
@@ -730,10 +759,16 @@ go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569 h1:nSQar3Y0E3VQF/VdZ8PTAilaXpER+d7ypdABCrpwMdg=
|
||||
go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df h1:shvkWr0NAZkg4nPuE3XrKP0VuBPijjk3TfX6Y6acFNg=
|
||||
go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15 h1:Z2sc4+v0JHV6Mn4kX1f2a5nruNjmV+Th32sugE8zwz8=
|
||||
go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -768,6 +803,7 @@ golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -798,6 +834,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181004145325-8469e314837c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -829,6 +866,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -864,6 +902,8 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
|
||||
@@ -888,6 +928,9 @@ gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 h1:E846t8CnR+lv5nE+VuiKTDG/v1U2stad0QzddfJC7kY=
|
||||
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
|
||||
@@ -9,6 +9,8 @@ RUN cd image/bin && \
|
||||
|
||||
FROM scratch
|
||||
COPY --from=base /image /
|
||||
RUN mkdir -p /etc && \
|
||||
echo 'hosts: files dns' > /etc/nsswitch.conf
|
||||
RUN chmod 1777 /tmp
|
||||
VOLUME /var/lib/kubelet
|
||||
VOLUME /var/lib/rancher/k3s
|
||||
|
||||
7
pkg/cli/cmds/dqlite.go
Normal file
7
pkg/cli/cmds/dqlite.go
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build dqlite
|
||||
|
||||
package cmds
|
||||
|
||||
const (
|
||||
hideDqlite = false
|
||||
)
|
||||
@@ -129,38 +129,6 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
|
||||
Destination: &ServerConfig.TokenFile,
|
||||
EnvVar: "K3S_TOKEN_FILE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "agent-token",
|
||||
Usage: "(cluster) Shared secret used to join agents to the cluster, but not agents",
|
||||
Destination: &ServerConfig.AgentToken,
|
||||
EnvVar: "K3S_AGENT_TOKEN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "agent-token-file",
|
||||
Usage: "(cluster) File containing the agent secret",
|
||||
Destination: &ServerConfig.AgentTokenFile,
|
||||
EnvVar: "K3S_AGENT_TOKEN_FILE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "server,s",
|
||||
Usage: "(cluster) Server to connect to, used to join a cluster",
|
||||
EnvVar: "K3S_URL",
|
||||
Destination: &ServerConfig.ServerURL,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cluster-init",
|
||||
Hidden: hideDqlite,
|
||||
Usage: "(cluster) Initialize new cluster master",
|
||||
EnvVar: "K3S_CLUSTER_INIT",
|
||||
Destination: &ServerConfig.ClusterInit,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cluster-reset",
|
||||
Hidden: hideDqlite,
|
||||
Usage: "(cluster) Forget all peers and become a single cluster new cluster master",
|
||||
EnvVar: "K3S_CLUSTER_RESET",
|
||||
Destination: &ServerConfig.ClusterReset,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "write-kubeconfig,o",
|
||||
Usage: "(client) Write kubeconfig for admin client to this file",
|
||||
@@ -260,6 +228,38 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
|
||||
Usage: "(experimental) Run rootless",
|
||||
Destination: &ServerConfig.Rootless,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "agent-token",
|
||||
Usage: "(experimental/cluster) Shared secret used to join agents to the cluster, but not agents",
|
||||
Destination: &ServerConfig.AgentToken,
|
||||
EnvVar: "K3S_AGENT_TOKEN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "agent-token-file",
|
||||
Usage: "(experimental/cluster) File containing the agent secret",
|
||||
Destination: &ServerConfig.AgentTokenFile,
|
||||
EnvVar: "K3S_AGENT_TOKEN_FILE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "server,s",
|
||||
Usage: "(experimental/cluster) Server to connect to, used to join a cluster",
|
||||
EnvVar: "K3S_URL",
|
||||
Destination: &ServerConfig.ServerURL,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cluster-init",
|
||||
Hidden: hideDqlite,
|
||||
Usage: "(experimental/cluster) Initialize new cluster master",
|
||||
EnvVar: "K3S_CLUSTER_INIT",
|
||||
Destination: &ServerConfig.ClusterInit,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "cluster-reset",
|
||||
Hidden: hideDqlite,
|
||||
Usage: "(experimental/cluster) Forget all peers and become a single cluster new cluster master",
|
||||
EnvVar: "K3S_CLUSTER_RESET",
|
||||
Destination: &ServerConfig.ClusterReset,
|
||||
},
|
||||
|
||||
// Hidden/Deprecated flags below
|
||||
|
||||
|
||||
@@ -2,42 +2,73 @@ package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/k3s/pkg/clientaccess"
|
||||
"github.com/rancher/k3s/pkg/daemons/config"
|
||||
"github.com/rancher/kine/pkg/client"
|
||||
"github.com/rancher/kine/pkg/endpoint"
|
||||
)
|
||||
|
||||
type Cluster struct {
|
||||
token string
|
||||
clientAccessInfo *clientaccess.Info
|
||||
config *config.Control
|
||||
runtime *config.ControlRuntime
|
||||
db interface{}
|
||||
runJoin bool
|
||||
storageStarted bool
|
||||
etcdConfig endpoint.ETCDConfig
|
||||
joining bool
|
||||
saveBootstrap bool
|
||||
storageClient client.Client
|
||||
}
|
||||
|
||||
func (c *Cluster) Start(ctx context.Context) error {
|
||||
join, err := c.shouldJoin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if join {
|
||||
if err := c.join(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.startClusterAndHTTPS(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if join {
|
||||
if c.runJoin {
|
||||
if err := c.postJoin(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.joined()
|
||||
if err := c.testClusterDB(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.saveBootstrap {
|
||||
if err := c.save(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.runJoin {
|
||||
if err := c.joined(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c.startStorage(ctx)
|
||||
}
|
||||
|
||||
func (c *Cluster) startStorage(ctx context.Context) error {
|
||||
if c.storageStarted {
|
||||
return nil
|
||||
}
|
||||
c.storageStarted = true
|
||||
|
||||
etcdConfig, err := endpoint.Listen(ctx, c.config.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.etcdConfig = etcdConfig
|
||||
c.config.Storage.Config = etcdConfig.TLSConfig
|
||||
c.config.Storage.Endpoint = strings.Join(etcdConfig.Endpoints, ",")
|
||||
c.config.NoLeaderElect = !etcdConfig.LeaderElect
|
||||
return nil
|
||||
}
|
||||
|
||||
func New(config *config.Control) *Cluster {
|
||||
|
||||
143
pkg/cluster/dqlite.go
Normal file
143
pkg/cluster/dqlite.go
Normal file
@@ -0,0 +1,143 @@
|
||||
// +build dqlite
|
||||
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
"github.com/rancher/dynamiclistener/factory"
|
||||
"github.com/rancher/k3s/pkg/clientaccess"
|
||||
"github.com/rancher/k3s/pkg/daemons/config"
|
||||
"github.com/rancher/k3s/pkg/dqlite"
|
||||
"github.com/rancher/kine/pkg/endpoint"
|
||||
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (c *Cluster) testClusterDB(ctx context.Context) error {
|
||||
if !c.dqliteEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
dqlite := c.db.(*dqlite.DQLite)
|
||||
for {
|
||||
if err := dqlite.Test(ctx); err != nil {
|
||||
logrus.Infof("Failed to test dqlite connection: %v", err)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(2 * time.Second):
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cluster) initClusterDB(ctx context.Context, l net.Listener, handler http.Handler) (net.Listener, http.Handler, error) {
|
||||
if !c.dqliteEnabled() {
|
||||
return l, handler, nil
|
||||
}
|
||||
|
||||
dqlite := dqlite.New(c.config.DataDir, c.config.AdvertiseIP, c.config.AdvertisePort, func() v1.NodeController {
|
||||
if c.runtime.Core == nil {
|
||||
return nil
|
||||
}
|
||||
return c.runtime.Core.Core().V1().Node()
|
||||
})
|
||||
|
||||
certs, err := toGetCerts(c.runtime)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
handler, err = dqlite.Start(ctx, c.config.ClusterInit, c.config.ClusterReset, certs, handler)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if c.config.ClusterReset {
|
||||
if err := dqlite.Reset(ctx); err == nil {
|
||||
logrus.Info("Cluster reset successful, now rejoin members")
|
||||
os.Exit(0)
|
||||
} else {
|
||||
logrus.Fatalf("Cluster reset failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.db = dqlite
|
||||
if !strings.HasPrefix(c.config.Storage.Endpoint, "dqlite://") {
|
||||
c.config.Storage = endpoint.Config{
|
||||
Endpoint: dqlite.StorageEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
return l, handler, err
|
||||
}
|
||||
|
||||
func (c *Cluster) dqliteEnabled() bool {
|
||||
stamp := filepath.Join(c.config.DataDir, "db", "state.dqlite")
|
||||
if _, err := os.Stat(stamp); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
driver, _ := endpoint.ParseStorageEndpoint(c.config.Storage.Endpoint)
|
||||
if driver == endpoint.DQLiteBackend {
|
||||
return true
|
||||
}
|
||||
|
||||
return c.config.Storage.Endpoint == "" && (c.config.ClusterInit || (c.config.Token != "" && c.config.JoinURL != ""))
|
||||
}
|
||||
|
||||
func (c *Cluster) postJoin(ctx context.Context) error {
|
||||
if !c.dqliteEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := clientaccess.Get("/db/info", c.clientAccessInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dqlite := c.db.(*dqlite.DQLite)
|
||||
var nodes []client.NodeInfo
|
||||
|
||||
if err := json.Unmarshal(resp, &nodes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dqlite.Join(ctx, nodes)
|
||||
}
|
||||
|
||||
func toGetCerts(runtime *config.ControlRuntime) (*dqlite.Certs, error) {
|
||||
clientCA, _, err := factory.LoadCerts(runtime.ClientCA, runtime.ClientCAKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ca, _, err := factory.LoadCerts(runtime.ServerCA, runtime.ServerCAKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientCert, err := tls.LoadX509KeyPair(runtime.ClientKubeAPICert, runtime.ClientKubeAPIKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dqlite.Certs{
|
||||
ServerTrust: ca,
|
||||
ClientTrust: clientCA,
|
||||
ClientCert: clientCert,
|
||||
}, nil
|
||||
}
|
||||
81
pkg/cluster/encrypt.go
Normal file
81
pkg/cluster/encrypt.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/rancher/k3s/pkg/token"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
func storageKey(passphrase string) string {
|
||||
d := sha256.New()
|
||||
d.Write([]byte(passphrase))
|
||||
return "/bootstrap/" + hex.EncodeToString(d.Sum(nil)[:])[:12]
|
||||
}
|
||||
|
||||
func keyHash(passphrase string) string {
|
||||
d := sha256.New()
|
||||
d.Write([]byte(passphrase))
|
||||
return hex.EncodeToString(d.Sum(nil)[:])[:12]
|
||||
}
|
||||
|
||||
func encrypt(passphrase string, plaintext []byte) ([]byte, error) {
|
||||
salt, err := token.Random(8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clearKey := pbkdf2.Key([]byte(passphrase), []byte(salt), 4096, 32, sha1.New)
|
||||
key, err := aes.NewCipher(clearKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
_, err = io.ReadFull(rand.Reader, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sealed := gcm.Seal(nonce, nonce, plaintext, nil)
|
||||
return []byte(salt + ":" + base64.StdEncoding.EncodeToString(sealed)), nil
|
||||
}
|
||||
|
||||
func decrypt(passphrase string, ciphertext []byte) ([]byte, error) {
|
||||
parts := strings.SplitN(string(ciphertext), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid cipher text, not : delimited")
|
||||
}
|
||||
|
||||
clearKey := pbkdf2.Key([]byte(passphrase), []byte(parts[0]), 4096, 32, sha1.New)
|
||||
key, err := aes.NewCipher(clearKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := base64.StdEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gcm.Open(nil, data[:gcm.NonceSize()], data[gcm.NonceSize():], nil)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package cluster
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -11,18 +12,38 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (c *Cluster) Join(ctx context.Context) error {
|
||||
runJoin, err := c.shouldJoin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.runJoin = runJoin
|
||||
|
||||
if runJoin {
|
||||
if err := c.join(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) shouldJoin() (bool, error) {
|
||||
if c.config.JoinURL == "" {
|
||||
return false, nil
|
||||
dqlite := c.dqliteEnabled()
|
||||
if dqlite {
|
||||
c.runtime.HTTPBootstrap = true
|
||||
if c.config.JoinURL == "" {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
stamp := filepath.Join(c.config.DataDir, "db/joined")
|
||||
stamp := c.joinStamp()
|
||||
if _, err := os.Stat(stamp); err == nil {
|
||||
logrus.Info("Already joined to cluster, not rejoining")
|
||||
logrus.Info("Cluster bootstrap already complete")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if c.config.Token == "" {
|
||||
if dqlite && c.config.Token == "" {
|
||||
return false, fmt.Errorf("K3S_TOKEN is required to join a cluster")
|
||||
}
|
||||
|
||||
@@ -46,14 +67,11 @@ func (c *Cluster) joined() error {
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
func (c *Cluster) join() error {
|
||||
c.runtime.Cluster.Join = true
|
||||
|
||||
func (c *Cluster) httpJoin() error {
|
||||
token, err := clientaccess.NormalizeAndValidateTokenForUser(c.config.JoinURL, c.config.Token, "server")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.token = token
|
||||
|
||||
info, err := clientaccess.ParseAndValidateToken(c.config.JoinURL, token)
|
||||
if err != nil {
|
||||
@@ -69,6 +87,20 @@ func (c *Cluster) join() error {
|
||||
return bootstrap.Read(bytes.NewBuffer(content), &c.runtime.ControlRuntimeBootstrap)
|
||||
}
|
||||
|
||||
func (c *Cluster) joinStamp() string {
|
||||
return filepath.Join(c.config.DataDir, "db/joined")
|
||||
func (c *Cluster) join(ctx context.Context) error {
|
||||
c.joining = true
|
||||
|
||||
if c.runtime.HTTPBootstrap {
|
||||
return c.httpJoin()
|
||||
}
|
||||
|
||||
if err := c.storageJoin(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) joinStamp() string {
|
||||
return filepath.Join(c.config.DataDir, "db/joined-"+keyHash(c.config.Token))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (c *Cluster) testClusterDB(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) initClusterDB(ctx context.Context, l net.Listener, handler http.Handler) (net.Listener, http.Handler, error) {
|
||||
return l, handler, nil
|
||||
}
|
||||
@@ -15,3 +19,7 @@ func (c *Cluster) initClusterDB(ctx context.Context, l net.Listener, handler htt
|
||||
func (c *Cluster) postJoin(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) dqliteEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
50
pkg/cluster/storage.go
Normal file
50
pkg/cluster/storage.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/rancher/k3s/pkg/bootstrap"
|
||||
"github.com/rancher/kine/pkg/client"
|
||||
)
|
||||
|
||||
func (c *Cluster) save(ctx context.Context) error {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := bootstrap.Write(buf, &c.runtime.ControlRuntimeBootstrap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := encrypt(c.config.Token, buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.storageClient.Create(ctx, storageKey(c.config.Token), data)
|
||||
}
|
||||
|
||||
func (c *Cluster) storageJoin(ctx context.Context) error {
|
||||
if err := c.startStorage(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storageClient, err := client.New(c.etcdConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.storageClient = storageClient
|
||||
|
||||
value, err := storageClient.Get(ctx, storageKey(c.config.Token))
|
||||
if err == client.ErrNotFound {
|
||||
c.saveBootstrap = true
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := decrypt(c.config.Token, value.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bootstrap.Read(bytes.NewBuffer(data), &c.runtime.ControlRuntimeBootstrap)
|
||||
}
|
||||
@@ -132,6 +132,8 @@ type ControlRuntimeBootstrap struct {
|
||||
type ControlRuntime struct {
|
||||
ControlRuntimeBootstrap
|
||||
|
||||
HTTPBootstrap bool
|
||||
|
||||
ClientKubeAPICert string
|
||||
ClientKubeAPIKey string
|
||||
NodePasswdFile string
|
||||
@@ -169,12 +171,7 @@ type ControlRuntime struct {
|
||||
ClientK3sControllerCert string
|
||||
ClientK3sControllerKey string
|
||||
|
||||
Cluster ClusterConfig
|
||||
Core *core.Factory
|
||||
}
|
||||
|
||||
type ClusterConfig struct {
|
||||
Join bool
|
||||
Core *core.Factory
|
||||
}
|
||||
|
||||
type ArgString []string
|
||||
|
||||
@@ -26,14 +26,14 @@ import (
|
||||
"github.com/rancher/k3s/pkg/daemons/config"
|
||||
"github.com/rancher/k3s/pkg/passwd"
|
||||
"github.com/rancher/k3s/pkg/token"
|
||||
"github.com/rancher/kine/pkg/endpoint"
|
||||
"github.com/rancher/wrangler-api/pkg/generated/controllers/rbac"
|
||||
"github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
ccmapp "k8s.io/kubernetes/cmd/cloud-controller-manager/app"
|
||||
app2 "k8s.io/kubernetes/cmd/controller-manager/app"
|
||||
"k8s.io/kubernetes/cmd/kube-apiserver/app"
|
||||
cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app"
|
||||
sapp "k8s.io/kubernetes/cmd/kube-scheduler/app"
|
||||
@@ -316,11 +316,13 @@ func prepare(ctx context.Context, config *config.Control, runtime *config.Contro
|
||||
runtime.ClientAuthProxyCert = path.Join(config.DataDir, "tls", "client-auth-proxy.crt")
|
||||
runtime.ClientAuthProxyKey = path.Join(config.DataDir, "tls", "client-auth-proxy.key")
|
||||
|
||||
if err := genCerts(config, runtime); err != nil {
|
||||
cluster := cluster.New(config)
|
||||
|
||||
if err := cluster.Join(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cluster.New(config).Start(ctx); err != nil {
|
||||
if err := genCerts(config, runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -336,23 +338,11 @@ func prepare(ctx context.Context, config *config.Control, runtime *config.Contro
|
||||
return err
|
||||
}
|
||||
|
||||
if err := prepareStorageBackend(ctx, config); err != nil {
|
||||
if err := readTokens(runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return readTokens(runtime)
|
||||
}
|
||||
|
||||
func prepareStorageBackend(ctx context.Context, config *config.Control) error {
|
||||
etcdConfig, err := endpoint.Listen(ctx, config.Storage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.Storage.Config = etcdConfig.TLSConfig
|
||||
config.Storage.Endpoint = strings.Join(etcdConfig.Endpoints, ",")
|
||||
config.NoLeaderElect = !etcdConfig.LeaderElect
|
||||
return nil
|
||||
return cluster.Start(ctx)
|
||||
}
|
||||
|
||||
func readTokens(runtime *config.ControlRuntime) error {
|
||||
@@ -858,25 +848,24 @@ func waitForAPIServer(ctx context.Context, runtime *config.ControlRuntime) error
|
||||
return err
|
||||
}
|
||||
|
||||
discoveryclient, err := discovery.NewDiscoveryClientForConfig(restConfig)
|
||||
k8sClient, err := kubernetes.NewForConfig(restConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < 60; i++ {
|
||||
info, err := discoveryclient.ServerVersion()
|
||||
if err == nil {
|
||||
logrus.Infof("apiserver %s is up and running", info)
|
||||
return nil
|
||||
}
|
||||
logrus.Infof("waiting for apiserver to become available")
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(time.Second):
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-promise(func() error { return app2.WaitForAPIServer(k8sClient, 5*time.Minute) }):
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("timeout waiting for apiserver")
|
||||
}
|
||||
|
||||
func promise(f func() error) <-chan error {
|
||||
c := make(chan error, 1)
|
||||
go func() {
|
||||
c <- f()
|
||||
close(c)
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
||||
156
pkg/dqlite/controller/client/controller.go
Normal file
156
pkg/dqlite/controller/client/controller.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
controllerv1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
allKey = "_all_"
|
||||
nodeID = "cluster.k3s.cattle.io/node-id"
|
||||
nodeAddress = "cluster.k3s.cattle.io/node-address"
|
||||
master = "node-role.kubernetes.io/master"
|
||||
)
|
||||
|
||||
func Register(ctx context.Context, nodeName string, nodeInfo client.NodeInfo,
|
||||
nodeStore client.NodeStore, nodes controllerv1.NodeController, opts []client.Option) {
|
||||
h := &handler{
|
||||
nodeStore: nodeStore,
|
||||
nodeController: nodes,
|
||||
nodeName: nodeName,
|
||||
id: strconv.FormatUint(nodeInfo.ID, 10),
|
||||
address: nodeInfo.Address,
|
||||
ctx: ctx,
|
||||
opts: opts,
|
||||
}
|
||||
nodes.OnChange(ctx, "dqlite-client", h.sync)
|
||||
nodes.OnRemove(ctx, "dqlite-client", h.onRemove)
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
nodeStore client.NodeStore
|
||||
nodeController controllerv1.NodeController
|
||||
nodeName string
|
||||
id string
|
||||
address string
|
||||
ctx context.Context
|
||||
opts []client.Option
|
||||
}
|
||||
|
||||
func (h *handler) sync(key string, node *v1.Node) (*v1.Node, error) {
|
||||
if key == allKey {
|
||||
return nil, h.updateNodeStore()
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if key == h.nodeName {
|
||||
return h.handleSelf(node)
|
||||
}
|
||||
|
||||
if node.Labels[master] == "true" {
|
||||
h.nodeController.Enqueue(allKey)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (h *handler) handleSelf(node *v1.Node) (*v1.Node, error) {
|
||||
if node.Annotations[nodeID] == h.id && node.Annotations[nodeAddress] == h.address {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
node = node.DeepCopy()
|
||||
if node.Annotations == nil {
|
||||
node.Annotations = map[string]string{}
|
||||
}
|
||||
node.Annotations[nodeID] = h.id
|
||||
node.Annotations[nodeAddress] = h.address
|
||||
|
||||
return h.nodeController.Update(node)
|
||||
}
|
||||
|
||||
func (h *handler) onRemove(key string, node *v1.Node) (*v1.Node, error) {
|
||||
address := node.Annotations[nodeAddress]
|
||||
if address == "" {
|
||||
return node, nil
|
||||
}
|
||||
return node, h.delete(address)
|
||||
}
|
||||
|
||||
func (h *handler) delete(address string) error {
|
||||
c, err := client.FindLeader(h.ctx, h.nodeStore, h.opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
members, err := c.Cluster(h.ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, member := range members {
|
||||
if member.Address == address {
|
||||
logrus.Infof("Removing %s %d from dqlite", member.Address, member.ID)
|
||||
return c.Remove(h.ctx, member.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) updateNodeStore() error {
|
||||
nodes, err := h.nodeController.Cache().List(labels.SelectorFromSet(labels.Set{
|
||||
master: "true",
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
nodeInfos []client.NodeInfo
|
||||
seen = map[string]bool{}
|
||||
)
|
||||
|
||||
for _, node := range nodes {
|
||||
address, ok := node.Annotations[nodeAddress]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
nodeIDStr, ok := node.Annotations[nodeID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(nodeIDStr, 10, 64)
|
||||
if err != nil {
|
||||
logrus.Errorf("invalid %s=%s, must be a number: %v", nodeID, nodeIDStr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !seen[address] {
|
||||
nodeInfos = append(nodeInfos, client.NodeInfo{
|
||||
ID: id,
|
||||
Address: address,
|
||||
})
|
||||
seen[address] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodeInfos) == 0 {
|
||||
return fmt.Errorf("not setting dqlient NodeStore len to 0")
|
||||
}
|
||||
|
||||
return h.nodeStore.Set(h.ctx, nodeInfos)
|
||||
}
|
||||
36
pkg/dqlite/dialer/dial.go
Normal file
36
pkg/dqlite/dialer/dial.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
"github.com/rancher/k3s/pkg/dqlite/pipe"
|
||||
)
|
||||
|
||||
func NewHTTPDialer(advertiseAddress, bindAddress string, tls *tls.Config) (client.DialFunc, error) {
|
||||
d := &dialer{
|
||||
advertiseAddress: advertiseAddress,
|
||||
bindAddress: bindAddress,
|
||||
tls: tls,
|
||||
}
|
||||
|
||||
return d.dial, nil
|
||||
}
|
||||
|
||||
type dialer struct {
|
||||
advertiseAddress string
|
||||
bindAddress string
|
||||
tls *tls.Config
|
||||
}
|
||||
|
||||
func (d *dialer) dial(ctx context.Context, address string) (net.Conn, error) {
|
||||
if address == d.advertiseAddress {
|
||||
return net.Dial("unix", d.bindAddress)
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://%s/db/connect", address)
|
||||
return pipe.ToHTTP(ctx, url, d.tls)
|
||||
}
|
||||
51
pkg/dqlite/join.go
Normal file
51
pkg/dqlite/join.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package dqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (d *DQLite) Test(ctx context.Context) error {
|
||||
var ips []string
|
||||
peers, err := d.NodeStore.Get(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, peer := range peers {
|
||||
ips = append(ips, peer.Address)
|
||||
}
|
||||
|
||||
logrus.Infof("Testing connection to peers %v", ips)
|
||||
return d.Join(ctx, nil)
|
||||
}
|
||||
|
||||
func (d *DQLite) Join(ctx context.Context, nodes []client.NodeInfo) error {
|
||||
if len(nodes) > 0 {
|
||||
if err := d.NodeStore.Set(ctx, nodes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
client, err := client.FindLeader(ctx, d.NodeStore, d.clientOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
current, err := client.Cluster(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, testNode := range current {
|
||||
if testNode.Address == d.NodeInfo.Address {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Infof("Joining dqlite cluster as address=%s, id=%d")
|
||||
return client.Add(ctx, d.NodeInfo)
|
||||
}
|
||||
27
pkg/dqlite/log.go
Normal file
27
pkg/dqlite/log.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package dqlite
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func log() client.LogFunc {
|
||||
return func(level client.LogLevel, s string, i ...interface{}) {
|
||||
switch level {
|
||||
case client.LogDebug:
|
||||
logrus.Debugf(s, i...)
|
||||
case client.LogError:
|
||||
logrus.Errorf(s, i...)
|
||||
case client.LogInfo:
|
||||
if strings.HasPrefix(s, "connected") {
|
||||
logrus.Debugf(s, i...)
|
||||
} else {
|
||||
logrus.Infof(s, i...)
|
||||
}
|
||||
case client.LogWarn:
|
||||
logrus.Warnf(s, i...)
|
||||
}
|
||||
}
|
||||
}
|
||||
56
pkg/dqlite/pipe/http.go
Normal file
56
pkg/dqlite/pipe/http.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ToHTTP(ctx context.Context, url string, tlsConfig *tls.Config) (net.Conn, error) {
|
||||
request, err := http.NewRequest(http.MethodPost, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request = request.WithContext(ctx)
|
||||
netDial := &net.Dialer{}
|
||||
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
netDial.Deadline = deadline
|
||||
}
|
||||
|
||||
conn, err := tls.DialWithDialer(netDial, "tcp", request.URL.Host, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "tls dial")
|
||||
}
|
||||
|
||||
err = request.Write(conn)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "request write")
|
||||
}
|
||||
|
||||
response, err := http.ReadResponse(bufio.NewReader(conn), request)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read request")
|
||||
}
|
||||
if response.StatusCode != http.StatusSwitchingProtocols {
|
||||
return nil, fmt.Errorf("expected 101 response, got: %d", response.StatusCode)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unix", "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to create unix listener")
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
if err := Unix(conn, listener.Addr().String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return listener.Accept()
|
||||
}
|
||||
46
pkg/dqlite/pipe/pipe.go
Normal file
46
pkg/dqlite/pipe/pipe.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/lxc/lxd/shared/eagain"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func UnixPiper(srcs <-chan net.Conn, bindAddress string) {
|
||||
for src := range srcs {
|
||||
go Unix(src, bindAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func Unix(src net.Conn, target string) error {
|
||||
dst, err := net.Dial("unix", target)
|
||||
if err != nil {
|
||||
src.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
Connect(src, dst)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Connect(src net.Conn, dst net.Conn) {
|
||||
go func() {
|
||||
_, err := io.Copy(eagain.Writer{Writer: dst}, eagain.Reader{Reader: src})
|
||||
if err != nil && err != io.EOF {
|
||||
logrus.Debugf("copy pipe src->dst closed: %v", err)
|
||||
}
|
||||
src.Close()
|
||||
dst.Close()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(eagain.Writer{Writer: src}, eagain.Reader{Reader: dst})
|
||||
if err != nil {
|
||||
logrus.Debugf("copy pipe dst->src closed: %v", err)
|
||||
}
|
||||
src.Close()
|
||||
dst.Close()
|
||||
}()
|
||||
}
|
||||
53
pkg/dqlite/proxy.go
Normal file
53
pkg/dqlite/proxy.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package dqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rancher/k3s/pkg/dqlite/pipe"
|
||||
)
|
||||
|
||||
var (
|
||||
upgradeResponse = []byte("HTTP/1.1 101 Switching Protocols\r\nUpgrade: dqlite\r\n\r\n")
|
||||
)
|
||||
|
||||
type proxy struct {
|
||||
conns chan net.Conn
|
||||
}
|
||||
|
||||
func newProxy(ctx context.Context, bindAddress string) http.Handler {
|
||||
p := &proxy{
|
||||
conns: make(chan net.Conn, 100),
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
close(p.conns)
|
||||
}()
|
||||
go pipe.UnixPiper(p.conns, bindAddress)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (h *proxy) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
hijacker, ok := rw.(http.Hijacker)
|
||||
if !ok {
|
||||
http.Error(rw, "failed to hijack", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
conn, _, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
err := errors.Wrap(err, "Hijack connection")
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if n, err := conn.Write(upgradeResponse); err != nil || n != len(upgradeResponse) {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
h.conns <- conn
|
||||
}
|
||||
15
pkg/dqlite/reset.go
Normal file
15
pkg/dqlite/reset.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package dqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (d *DQLite) Reset(ctx context.Context) error {
|
||||
logrus.Infof("Resetting cluster to single master")
|
||||
return d.node.Recover([]client.NodeInfo{
|
||||
d.NodeInfo,
|
||||
})
|
||||
}
|
||||
80
pkg/dqlite/router.go
Normal file
80
pkg/dqlite/router.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package dqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/canonical/go-dqlite"
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func router(ctx context.Context, next http.Handler, nodeInfo dqlite.NodeInfo, clientCA *x509.Certificate, clientCN string, bindAddress string) http.Handler {
|
||||
mux := mux.NewRouter()
|
||||
mux.Handle("/db/connect", newChecker(newProxy(ctx, bindAddress), clientCA, clientCN))
|
||||
mux.Handle("/db/info", infoHandler(ctx, nodeInfo, bindAddress))
|
||||
mux.NotFoundHandler = next
|
||||
return mux
|
||||
}
|
||||
|
||||
func infoHandler(ctx context.Context, nodeInfo dqlite.NodeInfo, bindAddress string) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
client, err := client.New(ctx, bindAddress, client.WithLogFunc(log()))
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
info, err := client.Cluster(ctx)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(rw).Encode(info)
|
||||
})
|
||||
}
|
||||
|
||||
type checker struct {
|
||||
next http.Handler
|
||||
verify x509.VerifyOptions
|
||||
cn string
|
||||
}
|
||||
|
||||
func newChecker(next http.Handler, ca *x509.Certificate, cn string) http.Handler {
|
||||
pool := x509.NewCertPool()
|
||||
pool.AddCert(ca)
|
||||
return &checker{
|
||||
next: next,
|
||||
verify: x509.VerifyOptions{
|
||||
Roots: pool,
|
||||
KeyUsages: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
},
|
||||
DNSName: cn,
|
||||
},
|
||||
cn: cn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *checker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if !c.check(req) {
|
||||
http.Error(rw, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
c.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (c *checker) check(r *http.Request) bool {
|
||||
for _, cert := range r.TLS.PeerCertificates {
|
||||
_, err := cert.Verify(c.verify)
|
||||
if err == nil {
|
||||
return cert.Subject.CommonName == c.cn
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
220
pkg/dqlite/server.go
Normal file
220
pkg/dqlite/server.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package dqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/canonical/go-dqlite"
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
"github.com/pkg/errors"
|
||||
controllerclient "github.com/rancher/k3s/pkg/dqlite/controller/client"
|
||||
"github.com/rancher/k3s/pkg/dqlite/dialer"
|
||||
dqlitedriver "github.com/rancher/kine/pkg/drivers/dqlite"
|
||||
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
)
|
||||
|
||||
type Certs struct {
|
||||
ServerTrust *x509.Certificate
|
||||
ClientTrust *x509.Certificate
|
||||
ClientCert tls.Certificate
|
||||
}
|
||||
|
||||
type DQLite struct {
|
||||
ClientCA string
|
||||
ClientCAKey string
|
||||
ClientCert string
|
||||
ClientCertKey string
|
||||
ServerCA string
|
||||
ServerCAKey string
|
||||
AdvertiseIP string
|
||||
AdvertisePort int
|
||||
DataDir string
|
||||
NodeStore client.NodeStore
|
||||
NodeInfo client.NodeInfo
|
||||
node *dqlite.Node
|
||||
StorageEndpoint string
|
||||
NodeControllerGetter NodeControllerGetter
|
||||
clientOpts []client.Option
|
||||
}
|
||||
|
||||
type NodeControllerGetter func() v1.NodeController
|
||||
|
||||
func New(dataDir, advertiseIP string, advertisePort int, getter NodeControllerGetter) *DQLite {
|
||||
return &DQLite{
|
||||
AdvertiseIP: advertiseIP,
|
||||
AdvertisePort: advertisePort,
|
||||
DataDir: dataDir,
|
||||
NodeControllerGetter: getter,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DQLite) Start(ctx context.Context, initCluster, resetCluster bool, certs *Certs, next http.Handler) (http.Handler, error) {
|
||||
bindAddress := d.getBindAddress()
|
||||
|
||||
clientTLSConfig, err := getClientTLSConfig(certs.ClientCert, certs.ServerTrust)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
advertise, err := getAdvertiseAddress(d.AdvertiseIP, d.AdvertisePort)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get advertise address")
|
||||
}
|
||||
|
||||
dial, err := getDialer(advertise, bindAddress, clientTLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dqlitedriver.Dialer = dial
|
||||
dqlitedriver.Logger = log()
|
||||
|
||||
d.clientOpts = append(d.clientOpts, client.WithDialFunc(dial), client.WithLogFunc(log()))
|
||||
|
||||
nodeInfo, node, err := getNode(d.DataDir, advertise, bindAddress, initCluster, dial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.NodeInfo = nodeInfo
|
||||
d.node = node
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
node.Close()
|
||||
}()
|
||||
|
||||
if err := d.nodeStore(ctx, initCluster); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go d.startController(ctx)
|
||||
|
||||
if !resetCluster {
|
||||
if err := node.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return router(ctx, next, nodeInfo, certs.ClientTrust, "kube-apiserver", bindAddress), nil
|
||||
}
|
||||
|
||||
func (d *DQLite) startController(ctx context.Context) {
|
||||
for {
|
||||
if nc := d.NodeControllerGetter(); nc != nil {
|
||||
if os.Getenv("NODE_NAME") == "" {
|
||||
logrus.Errorf("--disable-agent is not compatible with dqlite")
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
controllerclient.Register(ctx, os.Getenv("NODE_NAME"), d.NodeInfo, d.NodeStore, d.NodeControllerGetter(), d.clientOpts)
|
||||
}
|
||||
|
||||
func (d *DQLite) nodeStore(ctx context.Context, initCluster bool) error {
|
||||
peerDB := filepath.Join(d.DataDir, "db", "state.dqlite", "peers.db")
|
||||
ns, err := client.DefaultNodeStore(peerDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.NodeStore = ns
|
||||
d.StorageEndpoint = fmt.Sprintf("dqlite://?peer-file=%s", peerDB)
|
||||
if initCluster {
|
||||
if err := dqlitedriver.AddPeers(ctx, d.NodeStore, d.NodeInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAdvertiseAddress(advertiseIP string, advertisePort int) (string, error) {
|
||||
ip := advertiseIP
|
||||
if ip == "" {
|
||||
ipAddr, err := net.ChooseHostInterface()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ip = ipAddr.String()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", ip, advertisePort), nil
|
||||
}
|
||||
|
||||
func getClientTLSConfig(cert tls.Certificate, ca *x509.Certificate) (*tls.Config, error) {
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: x509.NewCertPool(),
|
||||
Certificates: []tls.Certificate{
|
||||
cert,
|
||||
},
|
||||
ServerName: "kubernetes",
|
||||
}
|
||||
tlsConfig.RootCAs.AddCert(ca)
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
func getDialer(advertiseAddress, bindAddress string, tlsConfig *tls.Config) (client.DialFunc, error) {
|
||||
return dialer.NewHTTPDialer(advertiseAddress, bindAddress, tlsConfig)
|
||||
}
|
||||
|
||||
func getNode(dataDir string, advertiseAddress, bindAddress string, initCluster bool, dial client.DialFunc) (dqlite.NodeInfo, *dqlite.Node, error) {
|
||||
id, err := getClusterID(initCluster, dataDir)
|
||||
if err != nil {
|
||||
return dqlite.NodeInfo{}, nil, errors.Wrap(err, "reading cluster id")
|
||||
}
|
||||
|
||||
dbDir := filepath.Join(dataDir, "db", "state.dqlite")
|
||||
|
||||
node, err := dqlite.New(id, advertiseAddress, dbDir,
|
||||
dqlite.WithBindAddress(bindAddress),
|
||||
dqlite.WithDialFunc(dial),
|
||||
dqlite.WithNetworkLatency(20*time.Millisecond))
|
||||
return dqlite.NodeInfo{
|
||||
ID: id,
|
||||
Address: advertiseAddress,
|
||||
}, node, err
|
||||
}
|
||||
|
||||
func getClusterID(initCluster bool, dataDir string) (uint64, error) {
|
||||
idFile := filepath.Join(dataDir, "db/state.dqlite/node-id")
|
||||
content, err := ioutil.ReadFile(idFile)
|
||||
if os.IsNotExist(err) {
|
||||
content = nil
|
||||
} else if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
idStr := strings.TrimSpace(string(content))
|
||||
if idStr == "" {
|
||||
if err := os.MkdirAll(filepath.Dir(idFile), 0700); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
id := rand.Uint64()
|
||||
if initCluster {
|
||||
id = 1
|
||||
}
|
||||
return id, ioutil.WriteFile(idFile, []byte(strconv.FormatUint(id, 10)), 0644)
|
||||
}
|
||||
|
||||
return strconv.ParseUint(idStr, 10, 64)
|
||||
}
|
||||
|
||||
func (d *DQLite) getBindAddress() string {
|
||||
// only anonymous works???
|
||||
return "@" + filepath.Join(d.DataDir, "db", "state.dqlite", "dqlite.sock")
|
||||
}
|
||||
@@ -49,7 +49,10 @@ func router(serverConfig *config.Control, tunnel http.Handler, ca []byte) http.H
|
||||
serverAuthed := mux.NewRouter()
|
||||
serverAuthed.Use(authMiddleware(serverConfig, "k3s:server"))
|
||||
serverAuthed.NotFoundHandler = nodeAuthed
|
||||
serverAuthed.Path("/v1-k3s/server-bootstrap").Handler(bootstrap.Handler(&serverConfig.Runtime.ControlRuntimeBootstrap))
|
||||
serverAuthed.Path("/db/info").Handler(nodeAuthed)
|
||||
if serverConfig.Runtime.HTTPBootstrap {
|
||||
serverAuthed.Path("/v1-k3s/server-bootstrap").Handler(bootstrap.Handler(&serverConfig.Runtime.ControlRuntimeBootstrap))
|
||||
}
|
||||
|
||||
staticDir := filepath.Join(serverConfig.DataDir, "static")
|
||||
router := mux.NewRouter()
|
||||
|
||||
@@ -48,7 +48,6 @@ rm -f \
|
||||
bin/k3s-agent \
|
||||
bin/hyperkube \
|
||||
bin/containerd \
|
||||
bin/cni \
|
||||
bin/runc \
|
||||
bin/containerd-shim \
|
||||
bin/containerd-shim-runc-v1 \
|
||||
@@ -65,6 +64,7 @@ cleanup() {
|
||||
}
|
||||
|
||||
INSTALLBIN=$(pwd)/bin
|
||||
if [ ! -x ${INSTALLBIN}/cni ]; then
|
||||
(
|
||||
echo Building cni
|
||||
TMPDIR=$(mktemp -d)
|
||||
@@ -74,6 +74,7 @@ INSTALLBIN=$(pwd)/bin
|
||||
cd $WORKDIR
|
||||
GOPATH=$TMPDIR CGO_ENABLED=0 go build -tags "$TAGS" -ldflags "$LDFLAGS $STATIC" -o $INSTALLBIN/cni
|
||||
)
|
||||
fi
|
||||
# echo Building agent
|
||||
# CGO_ENABLED=1 go build -tags "$TAGS" -ldflags "$VERSIONFLAGS $LDFLAGS $STATIC" -o bin/k3s-agent ./cmd/agent/main.go
|
||||
echo Building server
|
||||
@@ -83,8 +84,8 @@ ln -s containerd ./bin/k3s-server
|
||||
ln -s containerd ./bin/kubectl
|
||||
ln -s containerd ./bin/crictl
|
||||
ln -s containerd ./bin/ctr
|
||||
echo Building hyperkube
|
||||
CGO_ENABLED=1 go build -tags "$TAGS" -ldflags "$VERSIONFLAGS $LDFLAGS $STATIC_SQLITE" -o bin/hyperkube ./vendor/k8s.io/kubernetes/cmd/hyperkube/
|
||||
#echo Building hyperkube
|
||||
#CGO_ENABLED=1 go build -tags "$TAGS" -ldflags "$VERSIONFLAGS $LDFLAGS $STATIC_SQLITE" -o bin/hyperkube ./vendor/k8s.io/kubernetes/cmd/hyperkube/
|
||||
#echo Building ctr
|
||||
#CGO_ENABLED=1 go build -tags "$TAGS" -ldflags "$VERSIONFLAGS $LDFLAGS $STATIC_SQLITE" -o bin/ctr ./cmd/ctr/main.go
|
||||
# echo Building containerd
|
||||
|
||||
@@ -34,7 +34,6 @@ elif [ ${ARCH} = arm ]; then
|
||||
BIN_SUFFIX="-armhf"
|
||||
fi
|
||||
|
||||
cp -f ./bin/hyperkube dist/artifacts/hyperkube${BIN_SUFFIX}
|
||||
CMD_NAME=dist/artifacts/k3s${BIN_SUFFIX}
|
||||
|
||||
go generate
|
||||
|
||||
@@ -212,7 +212,7 @@ echo "Started ${K3S_AGENT}"
|
||||
# ---
|
||||
|
||||
timeout --foreground 1m bash -c 'wait-for-nodes 2'
|
||||
timeout --foreground 1m bash -c 'wait-for-services coredns local-path-provisioner metrics-server'
|
||||
timeout --foreground 3m bash -c 'wait-for-services coredns local-path-provisioner metrics-server'
|
||||
|
||||
if [ "$ARCH" = 'arm' ]; then
|
||||
echo "Aborting sonobuoy tests, images not available for $ARCH"
|
||||
|
||||
28
vendor/github.com/Rican7/retry/.travis.yml
generated
vendored
Normal file
28
vendor/github.com/Rican7/retry/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.6
|
||||
- tip
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
# Get all imported packages
|
||||
- make install-deps install-deps-dev
|
||||
|
||||
# Basic build errors
|
||||
- make build
|
||||
|
||||
script:
|
||||
# Lint
|
||||
- make format-lint
|
||||
- make import-lint
|
||||
- make copyright-lint
|
||||
|
||||
# Run tests
|
||||
- make test
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
fast_finish: true
|
||||
19
vendor/github.com/Rican7/retry/LICENSE
generated
vendored
Normal file
19
vendor/github.com/Rican7/retry/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2016 Trevor N. Suarez (Rican7)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
72
vendor/github.com/Rican7/retry/Makefile
generated
vendored
Normal file
72
vendor/github.com/Rican7/retry/Makefile
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
# Define some VCS context
|
||||
PARENT_BRANCH ?= master
|
||||
|
||||
# Set a default `min_confidence` value for `golint`
|
||||
GOLINT_MIN_CONFIDENCE ?= 0.3
|
||||
|
||||
# Set flags for `gofmt`
|
||||
GOFMT_FLAGS ?= -s
|
||||
|
||||
|
||||
all: install-deps build install
|
||||
|
||||
clean:
|
||||
go clean -i -x ./...
|
||||
|
||||
build:
|
||||
go build -v ./...
|
||||
|
||||
install:
|
||||
go install ./...
|
||||
|
||||
install-deps:
|
||||
go get -d -t ./...
|
||||
|
||||
install-deps-dev: install-deps
|
||||
go get github.com/golang/lint/golint
|
||||
go get golang.org/x/tools/cmd/goimports
|
||||
|
||||
update-deps:
|
||||
go get -d -t -u ./...
|
||||
|
||||
update-deps-dev: update-deps
|
||||
go get -u github.com/golang/lint/golint
|
||||
go get -u golang.org/x/tools/cmd/goimports
|
||||
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
test-with-coverage:
|
||||
go test -cover ./...
|
||||
|
||||
test-with-coverage-formatted:
|
||||
go test -cover ./... | column -t | sort -r
|
||||
|
||||
format-lint:
|
||||
errors=$$(gofmt -l ${GOFMT_FLAGS} .); if [ "$${errors}" != "" ]; then echo "$${errors}"; exit 1; fi
|
||||
|
||||
import-lint:
|
||||
errors=$$(goimports -l .); if [ "$${errors}" != "" ]; then echo "$${errors}"; exit 1; fi
|
||||
|
||||
style-lint:
|
||||
errors=$$(golint -min_confidence=${GOLINT_MIN_CONFIDENCE} ./...); if [ "$${errors}" != "" ]; then echo "$${errors}"; exit 1; fi
|
||||
|
||||
copyright-lint:
|
||||
@old_dates=$$(git diff --diff-filter=ACMRTUXB --name-only "${PARENT_BRANCH}" | xargs grep -E '[Cc]opyright(\s+)[©Cc]?(\s+)[0-9]{4}' | grep -E -v "[Cc]opyright(\s+)[©Cc]?(\s+)$$(date '+%Y')"); if [ "$${old_dates}" != "" ]; then printf "The following files contain outdated copyrights:\n$${old_dates}\n\nThis can be fixed with 'make copyright-fix'\n"; exit 1; fi
|
||||
|
||||
lint: install-deps-dev format-lint import-lint style-lint copyright-lint
|
||||
|
||||
format-fix:
|
||||
gofmt -w ${GOFMT_FLAGS} .
|
||||
|
||||
import-fix:
|
||||
goimports -w .
|
||||
|
||||
copyright-fix:
|
||||
@git diff --diff-filter=ACMRTUXB --name-only "${PARENT_BRANCH}" | xargs -I '_FILENAME' -- sh -c 'sed -i.bak "s/\([Cc]opyright\([[:space:]][©Cc]\{0,1\}[[:space:]]*\)\)[0-9]\{4\}/\1"$$(date '+%Y')"/g" _FILENAME && rm _FILENAME.bak'
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
|
||||
.PHONY: all clean build install install-deps install-deps-dev update-deps update-deps-dev test test-with-coverage test-with-coverage-formatted format-lint import-lint style-lint copyright-lint lint format-fix import-fix copyright-fix vet
|
||||
98
vendor/github.com/Rican7/retry/README.md
generated
vendored
Normal file
98
vendor/github.com/Rican7/retry/README.md
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
# retry
|
||||
|
||||
[](https://travis-ci.org/Rican7/retry)
|
||||
[](https://godoc.org/github.com/Rican7/retry)
|
||||
[](http://goreportcard.com/report/Rican7/retry)
|
||||
[](https://github.com/Rican7/retry/releases)
|
||||
|
||||
A simple, stateless, functional mechanism to perform actions repetitively until successful.
|
||||
|
||||
|
||||
## Project Status
|
||||
|
||||
This project is currently in "pre-release". While the code is heavily tested, the API may change.
|
||||
Vendor (commit or lock) this dependency if you plan on using it.
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
`go get github.com/Rican7/retry`
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
```go
|
||||
Retry(func(attempt uint) error {
|
||||
return nil // Do something that may or may not cause an error
|
||||
})
|
||||
```
|
||||
|
||||
### File Open
|
||||
|
||||
```go
|
||||
const logFilePath = "/var/log/myapp.log"
|
||||
|
||||
var logFile *os.File
|
||||
|
||||
err := Retry(func(attempt uint) error {
|
||||
var err error
|
||||
|
||||
logFile, err = os.Open(logFilePath)
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
if nil != err {
|
||||
log.Fatalf("Unable to open file %q with error %q", logFilePath, err)
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP request with strategies and backoff
|
||||
|
||||
```go
|
||||
var response *http.Response
|
||||
|
||||
action := func(attempt uint) error {
|
||||
var err error
|
||||
|
||||
response, err = http.Get("https://api.github.com/repos/Rican7/retry")
|
||||
|
||||
if nil == err && nil != response && response.StatusCode > 200 {
|
||||
err = fmt.Errorf("failed to fetch (attempt #%d) with status code: %d", attempt, response.StatusCode)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
err := Retry(
|
||||
action,
|
||||
strategy.Limit(5),
|
||||
strategy.Backoff(backoff.Fibonacci(10*time.Millisecond)),
|
||||
)
|
||||
|
||||
if nil != err {
|
||||
log.Fatalf("Failed to fetch repository with error %q", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Retry with backoff jitter
|
||||
|
||||
```go
|
||||
action := func(attempt uint) error {
|
||||
return errors.New("something happened")
|
||||
}
|
||||
|
||||
seed := time.Now().UnixNano()
|
||||
random := rand.New(rand.NewSource(seed))
|
||||
|
||||
Retry(
|
||||
action,
|
||||
strategy.Limit(5),
|
||||
strategy.BackoffWithJitter(
|
||||
backoff.BinaryExponential(10*time.Millisecond),
|
||||
jitter.Deviation(random, 0.5),
|
||||
),
|
||||
)
|
||||
```
|
||||
67
vendor/github.com/Rican7/retry/backoff/backoff.go
generated
vendored
Normal file
67
vendor/github.com/Rican7/retry/backoff/backoff.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Package backoff provides stateless methods of calculating durations based on
|
||||
// a number of attempts made.
|
||||
//
|
||||
// Copyright © 2016 Trevor N. Suarez (Rican7)
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Algorithm defines a function that calculates a time.Duration based on
|
||||
// the given retry attempt number.
|
||||
type Algorithm func(attempt uint) time.Duration
|
||||
|
||||
// Incremental creates a Algorithm that increments the initial duration
|
||||
// by the given increment for each attempt.
|
||||
func Incremental(initial, increment time.Duration) Algorithm {
|
||||
return func(attempt uint) time.Duration {
|
||||
return initial + (increment * time.Duration(attempt))
|
||||
}
|
||||
}
|
||||
|
||||
// Linear creates a Algorithm that linearly multiplies the factor
|
||||
// duration by the attempt number for each attempt.
|
||||
func Linear(factor time.Duration) Algorithm {
|
||||
return func(attempt uint) time.Duration {
|
||||
return (factor * time.Duration(attempt))
|
||||
}
|
||||
}
|
||||
|
||||
// Exponential creates a Algorithm that multiplies the factor duration by
|
||||
// an exponentially increasing factor for each attempt, where the factor is
|
||||
// calculated as the given base raised to the attempt number.
|
||||
func Exponential(factor time.Duration, base float64) Algorithm {
|
||||
return func(attempt uint) time.Duration {
|
||||
return (factor * time.Duration(math.Pow(base, float64(attempt))))
|
||||
}
|
||||
}
|
||||
|
||||
// BinaryExponential creates a Algorithm that multiplies the factor
|
||||
// duration by an exponentially increasing factor for each attempt, where the
|
||||
// factor is calculated as `2` raised to the attempt number (2^attempt).
|
||||
func BinaryExponential(factor time.Duration) Algorithm {
|
||||
return Exponential(factor, 2)
|
||||
}
|
||||
|
||||
// Fibonacci creates a Algorithm that multiplies the factor duration by
|
||||
// an increasing factor for each attempt, where the factor is the Nth number in
|
||||
// the Fibonacci sequence.
|
||||
func Fibonacci(factor time.Duration) Algorithm {
|
||||
return func(attempt uint) time.Duration {
|
||||
return (factor * time.Duration(fibonacciNumber(attempt)))
|
||||
}
|
||||
}
|
||||
|
||||
// fibonacciNumber calculates the Fibonacci sequence number for the given
|
||||
// sequence position.
|
||||
func fibonacciNumber(n uint) uint {
|
||||
if 0 == n {
|
||||
return 0
|
||||
} else if 1 == n {
|
||||
return 1
|
||||
} else {
|
||||
return fibonacciNumber(n-1) + fibonacciNumber(n-2)
|
||||
}
|
||||
}
|
||||
89
vendor/github.com/Rican7/retry/jitter/jitter.go
generated
vendored
Normal file
89
vendor/github.com/Rican7/retry/jitter/jitter.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Package jitter provides methods of transforming durations.
|
||||
//
|
||||
// Copyright © 2016 Trevor N. Suarez (Rican7)
|
||||
package jitter
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Transformation defines a function that calculates a time.Duration based on
|
||||
// the given duration.
|
||||
type Transformation func(duration time.Duration) time.Duration
|
||||
|
||||
// Full creates a Transformation that transforms a duration into a result
|
||||
// duration in [0, n) randomly, where n is the given duration.
|
||||
//
|
||||
// The given generator is what is used to determine the random transformation.
|
||||
// If a nil generator is passed, a default one will be provided.
|
||||
//
|
||||
// Inspired by https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||
func Full(generator *rand.Rand) Transformation {
|
||||
random := fallbackNewRandom(generator)
|
||||
|
||||
return func(duration time.Duration) time.Duration {
|
||||
return time.Duration(random.Int63n(int64(duration)))
|
||||
}
|
||||
}
|
||||
|
||||
// Equal creates a Transformation that transforms a duration into a result
|
||||
// duration in [n/2, n) randomly, where n is the given duration.
|
||||
//
|
||||
// The given generator is what is used to determine the random transformation.
|
||||
// If a nil generator is passed, a default one will be provided.
|
||||
//
|
||||
// Inspired by https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||
func Equal(generator *rand.Rand) Transformation {
|
||||
random := fallbackNewRandom(generator)
|
||||
|
||||
return func(duration time.Duration) time.Duration {
|
||||
return (duration / 2) + time.Duration(random.Int63n(int64(duration))/2)
|
||||
}
|
||||
}
|
||||
|
||||
// Deviation creates a Transformation that transforms a duration into a result
|
||||
// duration that deviates from the input randomly by a given factor.
|
||||
//
|
||||
// The given generator is what is used to determine the random transformation.
|
||||
// If a nil generator is passed, a default one will be provided.
|
||||
//
|
||||
// Inspired by https://developers.google.com/api-client-library/java/google-http-java-client/backoff
|
||||
func Deviation(generator *rand.Rand, factor float64) Transformation {
|
||||
random := fallbackNewRandom(generator)
|
||||
|
||||
return func(duration time.Duration) time.Duration {
|
||||
min := int64(math.Floor(float64(duration) * (1 - factor)))
|
||||
max := int64(math.Ceil(float64(duration) * (1 + factor)))
|
||||
|
||||
return time.Duration(random.Int63n(max-min) + min)
|
||||
}
|
||||
}
|
||||
|
||||
// NormalDistribution creates a Transformation that transforms a duration into a
|
||||
// result duration based on a normal distribution of the input and the given
|
||||
// standard deviation.
|
||||
//
|
||||
// The given generator is what is used to determine the random transformation.
|
||||
// If a nil generator is passed, a default one will be provided.
|
||||
func NormalDistribution(generator *rand.Rand, standardDeviation float64) Transformation {
|
||||
random := fallbackNewRandom(generator)
|
||||
|
||||
return func(duration time.Duration) time.Duration {
|
||||
return time.Duration(random.NormFloat64()*standardDeviation + float64(duration))
|
||||
}
|
||||
}
|
||||
|
||||
// fallbackNewRandom returns the passed in random instance if it's not nil,
|
||||
// and otherwise returns a new random instance seeded with the current time.
|
||||
func fallbackNewRandom(random *rand.Rand) *rand.Rand {
|
||||
// Return the passed in value if it's already not null
|
||||
if nil != random {
|
||||
return random
|
||||
}
|
||||
|
||||
seed := time.Now().UnixNano()
|
||||
|
||||
return rand.New(rand.NewSource(seed))
|
||||
}
|
||||
36
vendor/github.com/Rican7/retry/retry.go
generated
vendored
Normal file
36
vendor/github.com/Rican7/retry/retry.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// Package retry provides a simple, stateless, functional mechanism to perform
|
||||
// actions repetitively until successful.
|
||||
//
|
||||
// Copyright © 2016 Trevor N. Suarez (Rican7)
|
||||
package retry
|
||||
|
||||
import "github.com/Rican7/retry/strategy"
|
||||
|
||||
// Action defines a callable function that package retry can handle.
|
||||
type Action func(attempt uint) error
|
||||
|
||||
// Retry takes an action and performs it, repetitively, until successful.
|
||||
//
|
||||
// Optionally, strategies may be passed that assess whether or not an attempt
|
||||
// should be made.
|
||||
func Retry(action Action, strategies ...strategy.Strategy) error {
|
||||
var err error
|
||||
|
||||
for attempt := uint(0); (0 == attempt || nil != err) && shouldAttempt(attempt, strategies...); attempt++ {
|
||||
err = action(attempt)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// shouldAttempt evaluates the provided strategies with the given attempt to
|
||||
// determine if the Retry loop should make another attempt.
|
||||
func shouldAttempt(attempt uint, strategies ...strategy.Strategy) bool {
|
||||
shouldAttempt := true
|
||||
|
||||
for i := 0; shouldAttempt && i < len(strategies); i++ {
|
||||
shouldAttempt = shouldAttempt && strategies[i](attempt)
|
||||
}
|
||||
|
||||
return shouldAttempt
|
||||
}
|
||||
85
vendor/github.com/Rican7/retry/strategy/strategy.go
generated
vendored
Normal file
85
vendor/github.com/Rican7/retry/strategy/strategy.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// Package strategy provides a way to change the way that retry is performed.
|
||||
//
|
||||
// Copyright © 2016 Trevor N. Suarez (Rican7)
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Rican7/retry/backoff"
|
||||
"github.com/Rican7/retry/jitter"
|
||||
)
|
||||
|
||||
// Strategy defines a function that Retry calls before every successive attempt
|
||||
// to determine whether it should make the next attempt or not. Returning `true`
|
||||
// allows for the next attempt to be made. Returning `false` halts the retrying
|
||||
// process and returns the last error returned by the called Action.
|
||||
//
|
||||
// The strategy will be passed an "attempt" number on each successive retry
|
||||
// iteration, starting with a `0` value before the first attempt is actually
|
||||
// made. This allows for a pre-action delay, etc.
|
||||
type Strategy func(attempt uint) bool
|
||||
|
||||
// Limit creates a Strategy that limits the number of attempts that Retry will
|
||||
// make.
|
||||
func Limit(attemptLimit uint) Strategy {
|
||||
return func(attempt uint) bool {
|
||||
return (attempt <= attemptLimit)
|
||||
}
|
||||
}
|
||||
|
||||
// Delay creates a Strategy that waits the given duration before the first
|
||||
// attempt is made.
|
||||
func Delay(duration time.Duration) Strategy {
|
||||
return func(attempt uint) bool {
|
||||
if 0 == attempt {
|
||||
time.Sleep(duration)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Wait creates a Strategy that waits the given durations for each attempt after
|
||||
// the first. If the number of attempts is greater than the number of durations
|
||||
// provided, then the strategy uses the last duration provided.
|
||||
func Wait(durations ...time.Duration) Strategy {
|
||||
return func(attempt uint) bool {
|
||||
if 0 < attempt && 0 < len(durations) {
|
||||
durationIndex := int(attempt - 1)
|
||||
|
||||
if len(durations) <= durationIndex {
|
||||
durationIndex = len(durations) - 1
|
||||
}
|
||||
|
||||
time.Sleep(durations[durationIndex])
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Backoff creates a Strategy that waits before each attempt, with a duration as
|
||||
// defined by the given backoff.Algorithm.
|
||||
func Backoff(algorithm backoff.Algorithm) Strategy {
|
||||
return BackoffWithJitter(algorithm, noJitter())
|
||||
}
|
||||
|
||||
// BackoffWithJitter creates a Strategy that waits before each attempt, with a
|
||||
// duration as defined by the given backoff.Algorithm and jitter.Transformation.
|
||||
func BackoffWithJitter(algorithm backoff.Algorithm, transformation jitter.Transformation) Strategy {
|
||||
return func(attempt uint) bool {
|
||||
if 0 < attempt {
|
||||
time.Sleep(transformation(algorithm(attempt)))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// noJitter creates a jitter.Transformation that simply returns the input.
|
||||
func noJitter() jitter.Transformation {
|
||||
return func(duration time.Duration) time.Duration {
|
||||
return duration
|
||||
}
|
||||
}
|
||||
8
vendor/github.com/canonical/go-dqlite/.dir-locals.el
generated
vendored
Normal file
8
vendor/github.com/canonical/go-dqlite/.dir-locals.el
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
;;; Directory Local Variables
|
||||
;;; For more information see (info "(emacs) Directory Variables")
|
||||
((go-mode
|
||||
. ((go-test-args . "-tags libsqlite3 -timeout 10s")
|
||||
(eval
|
||||
. (set
|
||||
(make-local-variable 'flycheck-go-build-tags)
|
||||
'("libsqlite3"))))))
|
||||
4
vendor/github.com/canonical/go-dqlite/.gitignore
generated
vendored
Normal file
4
vendor/github.com/canonical/go-dqlite/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.sqlite
|
||||
demo
|
||||
profile.coverprofile
|
||||
overalls.coverprofile
|
||||
31
vendor/github.com/canonical/go-dqlite/.travis.yml
generated
vendored
Normal file
31
vendor/github.com/canonical/go-dqlite/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
dist: xenial
|
||||
language: go
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'ppa:dqlite/master'
|
||||
packages:
|
||||
- golint
|
||||
- libsqlite3-dev
|
||||
- libuv1-dev
|
||||
- libraft-dev
|
||||
- libco-dev
|
||||
- libdqlite-dev
|
||||
|
||||
before_install:
|
||||
- go get github.com/go-playground/overalls
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get github.com/tsenart/deadcode
|
||||
|
||||
script:
|
||||
- go get -t -tags libsqlite3 ./...
|
||||
- go vet -tags libsqlite3 ./...
|
||||
- golint
|
||||
- deadcode
|
||||
- project=github.com/canonical/go-dqlite
|
||||
- $GOPATH/bin/overalls -project $project -covermode=count -- -tags libsqlite3 -timeout 240s
|
||||
- $GOPATH/bin/goveralls -coverprofile overalls.coverprofile -service=travis-ci
|
||||
|
||||
go:
|
||||
- "1.12"
|
||||
1
vendor/github.com/canonical/go-dqlite/AUTHORS
generated
vendored
Normal file
1
vendor/github.com/canonical/go-dqlite/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Free Ekanayaka <free.ekanayaka@canonical.com>
|
||||
201
vendor/github.com/canonical/go-dqlite/LICENSE
generated
vendored
Normal file
201
vendor/github.com/canonical/go-dqlite/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
91
vendor/github.com/canonical/go-dqlite/README.md
generated
vendored
Normal file
91
vendor/github.com/canonical/go-dqlite/README.md
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
go-dqlite [](https://travis-ci.org/canonical/go-dqlite) [](https://coveralls.io/github/canonical/go-dqlite?branch=master) [](https://goreportcard.com/report/github.com/canonical/go-dqlite) [](https://godoc.org/github.com/canonical/go-dqlite)
|
||||
======
|
||||
|
||||
This repository provides the `go-dqlite` Go package, containing bindings for the
|
||||
[dqlite](https://github.com/canonical/dqlite) C library and a pure-Go
|
||||
client for the dqlite wire [protocol](https://github.com/canonical/dqlite/blob/master/doc/protocol.md).
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The best way to understand how to use the ```go-dqlite``` package is probably by
|
||||
looking at the source code of the [demo
|
||||
program](https://github.com/canonical/go-dqlite/tree/master/cmd/dqlite-demo) and
|
||||
use it as example.
|
||||
|
||||
Build
|
||||
-----
|
||||
|
||||
In order to use the go-dqlite package in your application, you'll need to have
|
||||
the [dqlite](https://github.com/canonical/dqlite) C library installed on your
|
||||
system, along with its dependencies. You then need to pass the ```-tags```
|
||||
argument to the Go tools when building or testing your packages, for example:
|
||||
|
||||
```bash
|
||||
go build -tags libsqlite3
|
||||
go test -tags libsqlite3
|
||||
```
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The documentation for this package can be found on [Godoc](http://godoc.org/github.com/canonical/go-dqlite).
|
||||
|
||||
Demo
|
||||
----
|
||||
|
||||
To see dqlite in action, either install the Debian package from the PPA:
|
||||
|
||||
```bash
|
||||
sudo add-apt-repository -y ppa:dqlite/v1
|
||||
sudo apt install dqlite
|
||||
```
|
||||
|
||||
or build the dqlite C library and its dependencies from source, as described
|
||||
[here](https://github.com/canonical/dqlite#build), and then run:
|
||||
|
||||
```
|
||||
go install -tags libsqlite3 ./cmd/dqlite-demo
|
||||
```
|
||||
|
||||
from the top-level directory of this repository.
|
||||
|
||||
Once the ```dqlite-demo``` binary is installed, start three nodes of the demo
|
||||
application, respectively with IDs ```1```, ```2,``` and ```3```:
|
||||
|
||||
```bash
|
||||
dqlite-demo start 1 &
|
||||
dqlite-demo start 2 &
|
||||
dqlite-demo start 3 &
|
||||
```
|
||||
|
||||
The node with ID ```1``` automatically becomes the leader of a single node
|
||||
cluster, while the nodes with IDs ```2``` and ```3``` are waiting to be notified
|
||||
what cluster they belong to. Let's make nodes ```2``` and ```3``` join the
|
||||
cluster:
|
||||
|
||||
```bash
|
||||
dqlite-demo add 2
|
||||
dqlite-demo add 3
|
||||
```
|
||||
|
||||
Now we can start using the cluster. The demo application is just a simple
|
||||
key/value store that stores data in a SQLite table. Let's insert a key pair:
|
||||
|
||||
```bash
|
||||
dqlite-demo update my-key my-value
|
||||
```
|
||||
|
||||
and then retrive it from the database:
|
||||
|
||||
```bash
|
||||
dqlite-demo query my-key
|
||||
```
|
||||
|
||||
Currently node ```1``` is the leader. If we stop it and then try to query the
|
||||
key again we'll notice that the ```query``` command hangs for a bit waiting for
|
||||
the failover to occur and for another node to step up as leader:
|
||||
|
||||
```
|
||||
kill -TERM %1; sleep 0.1; dqlite-demo query my-key
|
||||
```
|
||||
221
vendor/github.com/canonical/go-dqlite/client/client.go
generated
vendored
Normal file
221
vendor/github.com/canonical/go-dqlite/client/client.go
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/canonical/go-dqlite/internal/protocol"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DialFunc is a function that can be used to establish a network connection.
|
||||
type DialFunc = protocol.DialFunc
|
||||
|
||||
// Client speaks the dqlite wire protocol.
|
||||
type Client struct {
|
||||
protocol *protocol.Protocol
|
||||
}
|
||||
|
||||
// Option that can be used to tweak client parameters.
|
||||
type Option func(*options)
|
||||
|
||||
type options struct {
|
||||
DialFunc DialFunc
|
||||
LogFunc LogFunc
|
||||
}
|
||||
|
||||
// WithDialFunc sets a custom dial function for creating the client network
|
||||
// connection.
|
||||
func WithDialFunc(dial DialFunc) Option {
|
||||
return func(options *options) {
|
||||
options.DialFunc = dial
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogFunc sets a custom log function.
|
||||
// connection.
|
||||
func WithLogFunc(log LogFunc) Option {
|
||||
return func(options *options) {
|
||||
options.LogFunc = log
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new client connected to the dqlite node with the given
|
||||
// address.
|
||||
func New(ctx context.Context, address string, options ...Option) (*Client, error) {
|
||||
o := defaultOptions()
|
||||
|
||||
for _, option := range options {
|
||||
option(o)
|
||||
}
|
||||
// Establish the connection.
|
||||
conn, err := o.DialFunc(ctx, address)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to establish network connection")
|
||||
}
|
||||
|
||||
// Latest protocol version.
|
||||
proto := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(proto, protocol.VersionOne)
|
||||
|
||||
// Perform the protocol handshake.
|
||||
n, err := conn.Write(proto)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, errors.Wrap(err, "failed to send handshake")
|
||||
}
|
||||
if n != 8 {
|
||||
conn.Close()
|
||||
return nil, errors.Wrap(io.ErrShortWrite, "failed to send handshake")
|
||||
}
|
||||
|
||||
client := &Client{protocol: protocol.NewProtocol(protocol.VersionOne, conn)}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Leader returns information about the current leader, if any.
|
||||
func (c *Client) Leader(ctx context.Context) (*NodeInfo, error) {
|
||||
request := protocol.Message{}
|
||||
request.Init(16)
|
||||
response := protocol.Message{}
|
||||
response.Init(512)
|
||||
|
||||
protocol.EncodeLeader(&request)
|
||||
|
||||
if err := c.protocol.Call(ctx, &request, &response); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to send Leader request")
|
||||
}
|
||||
|
||||
id, address, err := protocol.DecodeNode(&response)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse Node response")
|
||||
}
|
||||
|
||||
info := &NodeInfo{ID: id, Address: address}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Cluster returns information about all nodes in the cluster.
|
||||
func (c *Client) Cluster(ctx context.Context) ([]NodeInfo, error) {
|
||||
request := protocol.Message{}
|
||||
request.Init(16)
|
||||
response := protocol.Message{}
|
||||
response.Init(512)
|
||||
|
||||
protocol.EncodeCluster(&request)
|
||||
|
||||
if err := c.protocol.Call(ctx, &request, &response); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to send Cluster request")
|
||||
}
|
||||
|
||||
servers, err := protocol.DecodeNodes(&response)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse Node response")
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// File holds the content of a single database file.
|
||||
type File struct {
|
||||
Name string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Dump the content of the database with the given name. Two files will be
|
||||
// returned, the first is the main database file (which has the same name as
|
||||
// the database), the second is the WAL file (which has the same name as the
|
||||
// database plus the suffix "-wal").
|
||||
func (c *Client) Dump(ctx context.Context, dbname string) ([]File, error) {
|
||||
request := protocol.Message{}
|
||||
request.Init(16)
|
||||
response := protocol.Message{}
|
||||
response.Init(512)
|
||||
|
||||
protocol.EncodeDump(&request, dbname)
|
||||
|
||||
if err := c.protocol.Call(ctx, &request, &response); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to send dump request")
|
||||
}
|
||||
|
||||
files, err := protocol.DecodeFiles(&response)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse files response")
|
||||
}
|
||||
defer files.Close()
|
||||
|
||||
dump := make([]File, 0)
|
||||
|
||||
for {
|
||||
name, data := files.Next()
|
||||
if name == "" {
|
||||
break
|
||||
}
|
||||
dump = append(dump, File{Name: name, Data: data})
|
||||
}
|
||||
|
||||
return dump, nil
|
||||
}
|
||||
|
||||
// Add a node to a cluster.
|
||||
func (c *Client) Add(ctx context.Context, node NodeInfo) error {
|
||||
request := protocol.Message{}
|
||||
request.Init(4096)
|
||||
response := protocol.Message{}
|
||||
response.Init(4096)
|
||||
|
||||
protocol.EncodeJoin(&request, node.ID, node.Address)
|
||||
|
||||
if err := c.protocol.Call(ctx, &request, &response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
protocol.EncodePromote(&request, node.ID)
|
||||
|
||||
if err := c.protocol.Call(ctx, &request, &response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove a node from the cluster.
|
||||
func (c *Client) Remove(ctx context.Context, id uint64) error {
|
||||
request := protocol.Message{}
|
||||
request.Init(4096)
|
||||
response := protocol.Message{}
|
||||
response.Init(4096)
|
||||
|
||||
protocol.EncodeRemove(&request, id)
|
||||
|
||||
if err := c.protocol.Call(ctx, &request, &response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the client.
|
||||
func (c *Client) Close() error {
|
||||
return c.protocol.Close()
|
||||
}
|
||||
|
||||
// Create a client options object with sane defaults.
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
DialFunc: DefaultDialFunc,
|
||||
LogFunc: DefaultLogFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultDialFunc(ctx context.Context, address string) (net.Conn, error) {
|
||||
if strings.HasPrefix(address, "@") {
|
||||
return protocol.UnixDial(ctx, address)
|
||||
}
|
||||
return protocol.TCPDial(ctx, address)
|
||||
}
|
||||
35
vendor/github.com/canonical/go-dqlite/client/leader.go
generated
vendored
Normal file
35
vendor/github.com/canonical/go-dqlite/client/leader.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/Rican7/retry/backoff"
|
||||
"github.com/Rican7/retry/strategy"
|
||||
"github.com/canonical/go-dqlite/internal/protocol"
|
||||
)
|
||||
|
||||
// FindLeader returns a Client connected to the current cluster leader, if any.
|
||||
func FindLeader(ctx context.Context, store NodeStore, options ...Option) (*Client, error) {
|
||||
o := defaultOptions()
|
||||
|
||||
for _, option := range options {
|
||||
option(o)
|
||||
}
|
||||
|
||||
config := protocol.Config{
|
||||
Dial: o.DialFunc,
|
||||
AttemptTimeout: time.Second,
|
||||
RetryStrategies: []strategy.Strategy{
|
||||
strategy.Backoff(backoff.BinaryExponential(time.Millisecond))},
|
||||
}
|
||||
connector := protocol.NewConnector(0, store, config, o.LogFunc)
|
||||
protocol, err := connector.Connect(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &Client{protocol: protocol}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
30
vendor/github.com/canonical/go-dqlite/client/log.go
generated
vendored
Normal file
30
vendor/github.com/canonical/go-dqlite/client/log.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/canonical/go-dqlite/internal/logging"
|
||||
)
|
||||
|
||||
// LogFunc is a function that can be used for logging.
|
||||
type LogFunc = logging.Func
|
||||
|
||||
// LogLevel defines the logging level.
|
||||
type LogLevel = logging.Level
|
||||
|
||||
// Available logging levels.
|
||||
const (
|
||||
LogDebug = logging.Debug
|
||||
LogInfo = logging.Info
|
||||
LogWarn = logging.Warn
|
||||
LogError = logging.Error
|
||||
)
|
||||
|
||||
// DefaultLogFunc emits messages using the stdlib's logger.
|
||||
func DefaultLogFunc(l LogLevel, format string, a ...interface{}) {
|
||||
logger := log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds)
|
||||
format = fmt.Sprintf("[%s]: %s", l.String(), format)
|
||||
logger.Printf(format, a...)
|
||||
}
|
||||
136
vendor/github.com/canonical/go-dqlite/client/store.go
generated
vendored
Normal file
136
vendor/github.com/canonical/go-dqlite/client/store.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/canonical/go-dqlite/internal/protocol"
|
||||
_ "github.com/mattn/go-sqlite3" // Go SQLite bindings
|
||||
)
|
||||
|
||||
// NodeStore is used by a dqlite client to get an initial list of candidate
|
||||
// dqlite nodes that it can dial in order to find a leader dqlite node to use.
|
||||
type NodeStore = protocol.NodeStore
|
||||
|
||||
// NodeInfo holds information about a single server.
|
||||
type NodeInfo = protocol.NodeInfo
|
||||
|
||||
// InmemNodeStore keeps the list of target dqlite nodes in memory.
|
||||
type InmemNodeStore = protocol.InmemNodeStore
|
||||
|
||||
// NewInmemNodeStore creates NodeStore which stores its data in-memory.
|
||||
var NewInmemNodeStore = protocol.NewInmemNodeStore
|
||||
|
||||
// DatabaseNodeStore persists a list addresses of dqlite nodes in a SQL table.
|
||||
type DatabaseNodeStore struct {
|
||||
db *sql.DB // Database handle to use.
|
||||
schema string // Name of the schema holding the servers table.
|
||||
table string // Name of the servers table.
|
||||
column string // Column name in the servers table holding the server address.
|
||||
}
|
||||
|
||||
// DefaultNodeStore creates a new NodeStore using the given filename to
|
||||
// open a SQLite database, with default names for the schema, table and column
|
||||
// parameters.
|
||||
//
|
||||
// It also creates the table if it doesn't exist yet.
|
||||
func DefaultNodeStore(filename string) (*DatabaseNodeStore, error) {
|
||||
// Open the database.
|
||||
db, err := sql.Open("sqlite3", filename)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to open database")
|
||||
}
|
||||
|
||||
// Since we're setting SQLite single-thread mode, we need to have one
|
||||
// connection at most.
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
// Create the servers table if it does not exist yet.
|
||||
_, err = db.Exec("CREATE TABLE IF NOT EXISTS servers (address TEXT, UNIQUE(address))")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create servers table")
|
||||
}
|
||||
|
||||
store := NewNodeStore(db, "main", "servers", "address")
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// NewNodeStore creates a new NodeStore.
|
||||
func NewNodeStore(db *sql.DB, schema, table, column string) *DatabaseNodeStore {
|
||||
return &DatabaseNodeStore{
|
||||
db: db,
|
||||
schema: schema,
|
||||
table: table,
|
||||
column: column,
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current servers.
|
||||
func (d *DatabaseNodeStore) Get(ctx context.Context) ([]NodeInfo, error) {
|
||||
tx, err := d.db.Begin()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to begin transaction")
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
query := fmt.Sprintf("SELECT %s FROM %s.%s", d.column, d.schema, d.table)
|
||||
rows, err := tx.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to query servers table")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
servers := make([]NodeInfo, 0)
|
||||
for rows.Next() {
|
||||
var address string
|
||||
err := rows.Scan(&address)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to fetch server address")
|
||||
}
|
||||
servers = append(servers, NodeInfo{ID: 1, Address: address})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, errors.Wrap(err, "result set failure")
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// Set the servers addresses.
|
||||
func (d *DatabaseNodeStore) Set(ctx context.Context, servers []NodeInfo) error {
|
||||
tx, err := d.db.Begin()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to begin transaction")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("DELETE FROM %s.%s", d.schema, d.table)
|
||||
if _, err := tx.ExecContext(ctx, query); err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "failed to delete existing servers rows")
|
||||
}
|
||||
|
||||
query = fmt.Sprintf("INSERT INTO %s.%s(%s) VALUES (?)", d.schema, d.table, d.column)
|
||||
stmt, err := tx.PrepareContext(ctx, query)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "failed to prepare insert statement")
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
for _, server := range servers {
|
||||
if _, err := stmt.ExecContext(ctx, server.Address); err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrapf(err, "failed to insert server %s", server.Address)
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return errors.Wrap(err, "failed to commit transaction")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
670
vendor/github.com/canonical/go-dqlite/driver/driver.go
generated
vendored
Normal file
670
vendor/github.com/canonical/go-dqlite/driver/driver.go
generated
vendored
Normal file
@@ -0,0 +1,670 @@
|
||||
// Copyright 2017 Canonical Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package driver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"io"
|
||||
"net"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/Rican7/retry/backoff"
|
||||
"github.com/Rican7/retry/strategy"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
"github.com/canonical/go-dqlite/internal/bindings"
|
||||
"github.com/canonical/go-dqlite/internal/protocol"
|
||||
)
|
||||
|
||||
// Driver perform queries against a dqlite server.
|
||||
type Driver struct {
|
||||
log client.LogFunc // Log function to use
|
||||
store client.NodeStore // Holds addresses of dqlite servers
|
||||
context context.Context // Global cancellation context
|
||||
connectionTimeout time.Duration // Max time to wait for a new connection
|
||||
contextTimeout time.Duration // Default client context timeout.
|
||||
clientConfig protocol.Config // Configuration for dqlite client instances
|
||||
}
|
||||
|
||||
// Error is returned in case of database errors.
|
||||
type Error = bindings.Error
|
||||
|
||||
// Error codes. Values here mostly overlap with native SQLite codes.
|
||||
const (
|
||||
ErrBusy = 5
|
||||
errIoErr = 10
|
||||
errIoErrNotLeader = errIoErr | 32<<8
|
||||
errIoErrLeadershipLost = errIoErr | (33 << 8)
|
||||
)
|
||||
|
||||
// Option can be used to tweak driver parameters.
|
||||
type Option func(*options)
|
||||
|
||||
// NodeStore is a convenience alias of client.NodeStore.
|
||||
type NodeStore = client.NodeStore
|
||||
|
||||
// NodeInfo is a convenience alias of client.NodeInfo.
|
||||
type NodeInfo = client.NodeInfo
|
||||
|
||||
// DefaultNodeStore is a convenience alias of client.DefaultNodeStore.
|
||||
var DefaultNodeStore = client.DefaultNodeStore
|
||||
|
||||
// WithLogFunc sets a custom logging function.
|
||||
func WithLogFunc(log client.LogFunc) Option {
|
||||
return func(options *options) {
|
||||
options.Log = log
|
||||
}
|
||||
}
|
||||
|
||||
// DialFunc is a function that can be used to establish a network connection
|
||||
// with a dqlite node.
|
||||
type DialFunc = protocol.DialFunc
|
||||
|
||||
// WithDialFunc sets a custom dial function.
|
||||
func WithDialFunc(dial DialFunc) Option {
|
||||
return func(options *options) {
|
||||
options.Dial = protocol.DialFunc(dial)
|
||||
}
|
||||
}
|
||||
|
||||
// WithConnectionTimeout sets the connection timeout.
|
||||
//
|
||||
// If not used, the default is 5 seconds.
|
||||
func WithConnectionTimeout(timeout time.Duration) Option {
|
||||
return func(options *options) {
|
||||
options.ConnectionTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// WithConnectionBackoffFactor sets the exponential backoff factor for retrying
|
||||
// failed connection attempts.
|
||||
//
|
||||
// If not used, the default is 50 milliseconds.
|
||||
func WithConnectionBackoffFactor(factor time.Duration) Option {
|
||||
return func(options *options) {
|
||||
options.ConnectionBackoffFactor = factor
|
||||
}
|
||||
}
|
||||
|
||||
// WithConnectionBackoffCap sets the maximum connection retry backoff value,
|
||||
// (regardless of the backoff factor) for retrying failed connection attempts.
|
||||
//
|
||||
// If not used, the default is 1 second.
|
||||
func WithConnectionBackoffCap(cap time.Duration) Option {
|
||||
return func(options *options) {
|
||||
options.ConnectionBackoffCap = cap
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext sets a global cancellation context.
|
||||
func WithContext(context context.Context) Option {
|
||||
return func(options *options) {
|
||||
options.Context = context
|
||||
}
|
||||
}
|
||||
|
||||
// WithContextTimeout sets the default client context timeout when no context
|
||||
// deadline is provided.
|
||||
//
|
||||
// If not used, the default is 5 seconds.
|
||||
func WithContextTimeout(timeout time.Duration) Option {
|
||||
return func(options *options) {
|
||||
options.ContextTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// NewDriver creates a new dqlite driver, which also implements the
|
||||
// driver.Driver interface.
|
||||
func New(store client.NodeStore, options ...Option) (*Driver, error) {
|
||||
o := defaultOptions()
|
||||
|
||||
for _, option := range options {
|
||||
option(o)
|
||||
}
|
||||
|
||||
driver := &Driver{
|
||||
log: o.Log,
|
||||
store: store,
|
||||
context: o.Context,
|
||||
connectionTimeout: o.ConnectionTimeout,
|
||||
contextTimeout: o.ContextTimeout,
|
||||
}
|
||||
|
||||
driver.clientConfig.Dial = o.Dial
|
||||
driver.clientConfig.AttemptTimeout = 5 * time.Second
|
||||
driver.clientConfig.RetryStrategies = []strategy.Strategy{
|
||||
driverConnectionRetryStrategy(
|
||||
o.ConnectionBackoffFactor,
|
||||
o.ConnectionBackoffCap,
|
||||
),
|
||||
}
|
||||
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
// Hold configuration options for a dqlite driver.
|
||||
type options struct {
|
||||
Log client.LogFunc
|
||||
Dial protocol.DialFunc
|
||||
ConnectionTimeout time.Duration
|
||||
ContextTimeout time.Duration
|
||||
ConnectionBackoffFactor time.Duration
|
||||
ConnectionBackoffCap time.Duration
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// Create a options object with sane defaults.
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
Log: client.DefaultLogFunc,
|
||||
Dial: client.DefaultDialFunc,
|
||||
ConnectionTimeout: 15 * time.Second,
|
||||
ContextTimeout: 2 * time.Second,
|
||||
ConnectionBackoffFactor: 50 * time.Millisecond,
|
||||
ConnectionBackoffCap: time.Second,
|
||||
Context: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// Return a retry strategy with jittered exponential backoff, capped at the
|
||||
// given amount of time.
|
||||
func driverConnectionRetryStrategy(factor, cap time.Duration) strategy.Strategy {
|
||||
backoff := backoff.BinaryExponential(factor)
|
||||
|
||||
return func(attempt uint) bool {
|
||||
if attempt > 0 {
|
||||
duration := backoff(attempt)
|
||||
if duration > cap {
|
||||
duration = cap
|
||||
}
|
||||
time.Sleep(duration)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Open establishes a new connection to a SQLite database on the dqlite server.
|
||||
//
|
||||
// The given name must be a pure file name without any directory segment,
|
||||
// dqlite will connect to a database with that name in its data directory.
|
||||
//
|
||||
// Query parameters are always valid except for "mode=memory".
|
||||
//
|
||||
// If this node is not the leader, or the leader is unknown an ErrNotLeader
|
||||
// error is returned.
|
||||
func (d *Driver) Open(uri string) (driver.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(d.context, d.connectionTimeout)
|
||||
defer cancel()
|
||||
|
||||
// TODO: generate a client ID.
|
||||
connector := protocol.NewConnector(0, d.store, d.clientConfig, d.log)
|
||||
|
||||
conn := &Conn{
|
||||
log: d.log,
|
||||
contextTimeout: d.contextTimeout,
|
||||
}
|
||||
|
||||
var err error
|
||||
conn.protocol, err = connector.Connect(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create dqlite connection")
|
||||
}
|
||||
conn.protocol.SetContextTimeout(d.contextTimeout)
|
||||
|
||||
conn.request.Init(4096)
|
||||
conn.response.Init(4096)
|
||||
|
||||
defer conn.request.Reset()
|
||||
defer conn.response.Reset()
|
||||
|
||||
protocol.EncodeOpen(&conn.request, uri, 0, "volatile")
|
||||
|
||||
if err := conn.protocol.Call(ctx, &conn.request, &conn.response); err != nil {
|
||||
conn.protocol.Close()
|
||||
return nil, errors.Wrap(err, "failed to open database")
|
||||
}
|
||||
|
||||
conn.id, err = protocol.DecodeDb(&conn.response)
|
||||
if err != nil {
|
||||
conn.protocol.Close()
|
||||
return nil, errors.Wrap(err, "failed to open database")
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// SetContextTimeout sets the default client timeout when no context deadline
|
||||
// is provided.
|
||||
func (d *Driver) SetContextTimeout(timeout time.Duration) {
|
||||
d.contextTimeout = timeout
|
||||
}
|
||||
|
||||
// ErrNoAvailableLeader is returned as root cause of Open() if there's no
|
||||
// leader available in the cluster.
|
||||
var ErrNoAvailableLeader = protocol.ErrNoAvailableLeader
|
||||
|
||||
// Conn implements the sql.Conn interface.
|
||||
type Conn struct {
|
||||
log client.LogFunc
|
||||
protocol *protocol.Protocol
|
||||
request protocol.Message
|
||||
response protocol.Message
|
||||
id uint32 // Database ID.
|
||||
contextTimeout time.Duration
|
||||
}
|
||||
|
||||
// PrepareContext returns a prepared statement, bound to this connection.
|
||||
// context is for the preparation of the statement, it must not store the
|
||||
// context within the statement itself.
|
||||
func (c *Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||
defer c.request.Reset()
|
||||
defer c.response.Reset()
|
||||
|
||||
stmt := &Stmt{
|
||||
protocol: c.protocol,
|
||||
request: &c.request,
|
||||
response: &c.response,
|
||||
}
|
||||
|
||||
protocol.EncodePrepare(&c.request, uint64(c.id), query)
|
||||
|
||||
if err := c.protocol.Call(ctx, &c.request, &c.response); err != nil {
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
var err error
|
||||
stmt.db, stmt.id, stmt.params, err = protocol.DecodeStmt(&c.response)
|
||||
if err != nil {
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
return stmt, nil
|
||||
}
|
||||
|
||||
// Prepare returns a prepared statement, bound to this connection.
|
||||
func (c *Conn) Prepare(query string) (driver.Stmt, error) {
|
||||
return c.PrepareContext(context.Background(), query)
|
||||
}
|
||||
|
||||
// ExecContext is an optional interface that may be implemented by a Conn.
|
||||
func (c *Conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||
defer c.request.Reset()
|
||||
defer c.response.Reset()
|
||||
|
||||
protocol.EncodeExecSQL(&c.request, uint64(c.id), query, args)
|
||||
|
||||
if err := c.protocol.Call(ctx, &c.request, &c.response); err != nil {
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
result, err := protocol.DecodeResult(&c.response)
|
||||
if err != nil {
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
return &Result{result: result}, nil
|
||||
}
|
||||
|
||||
// Query is an optional interface that may be implemented by a Conn.
|
||||
func (c *Conn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||
return c.QueryContext(context.Background(), query, valuesToNamedValues(args))
|
||||
}
|
||||
|
||||
// QueryContext is an optional interface that may be implemented by a Conn.
|
||||
func (c *Conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||
defer c.request.Reset()
|
||||
|
||||
protocol.EncodeQuerySQL(&c.request, uint64(c.id), query, args)
|
||||
|
||||
if err := c.protocol.Call(ctx, &c.request, &c.response); err != nil {
|
||||
c.response.Reset()
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
rows, err := protocol.DecodeRows(&c.response)
|
||||
if err != nil {
|
||||
c.response.Reset()
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
return &Rows{ctx: ctx, request: &c.request, response: &c.response, protocol: c.protocol, rows: rows}, nil
|
||||
}
|
||||
|
||||
// Exec is an optional interface that may be implemented by a Conn.
|
||||
func (c *Conn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||
return c.ExecContext(context.Background(), query, valuesToNamedValues(args))
|
||||
}
|
||||
|
||||
// Close invalidates and potentially stops any current prepared statements and
|
||||
// transactions, marking this connection as no longer in use.
|
||||
//
|
||||
// Because the sql package maintains a free pool of connections and only calls
|
||||
// Close when there's a surplus of idle connections, it shouldn't be necessary
|
||||
// for drivers to do their own connection caching.
|
||||
func (c *Conn) Close() error {
|
||||
return c.protocol.Close()
|
||||
}
|
||||
|
||||
// BeginTx starts and returns a new transaction. If the context is canceled by
|
||||
// the user the sql package will call Tx.Rollback before discarding and closing
|
||||
// the connection.
|
||||
//
|
||||
// This must check opts.Isolation to determine if there is a set isolation
|
||||
// level. If the driver does not support a non-default level and one is set or
|
||||
// if there is a non-default isolation level that is not supported, an error
|
||||
// must be returned.
|
||||
//
|
||||
// This must also check opts.ReadOnly to determine if the read-only value is
|
||||
// true to either set the read-only transaction property if supported or return
|
||||
// an error if it is not supported.
|
||||
func (c *Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||
if _, err := c.ExecContext(ctx, "BEGIN", nil); err != nil {
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
tx := &Tx{
|
||||
conn: c,
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// Begin starts and returns a new transaction.
|
||||
//
|
||||
// Deprecated: Drivers should implement ConnBeginTx instead (or additionally).
|
||||
func (c *Conn) Begin() (driver.Tx, error) {
|
||||
return c.BeginTx(context.Background(), driver.TxOptions{})
|
||||
}
|
||||
|
||||
// Tx is a transaction.
|
||||
type Tx struct {
|
||||
conn *Conn
|
||||
}
|
||||
|
||||
// Commit the transaction.
|
||||
func (tx *Tx) Commit() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tx.conn.contextTimeout)
|
||||
defer cancel()
|
||||
|
||||
if _, err := tx.conn.ExecContext(ctx, "COMMIT", nil); err != nil {
|
||||
return driverError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rollback the transaction.
|
||||
func (tx *Tx) Rollback() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tx.conn.contextTimeout)
|
||||
defer cancel()
|
||||
|
||||
if _, err := tx.conn.ExecContext(ctx, "ROLLBACK", nil); err != nil {
|
||||
return driverError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stmt is a prepared statement. It is bound to a Conn and not
|
||||
// used by multiple goroutines concurrently.
|
||||
type Stmt struct {
|
||||
protocol *protocol.Protocol
|
||||
request *protocol.Message
|
||||
response *protocol.Message
|
||||
db uint32
|
||||
id uint32
|
||||
params uint64
|
||||
}
|
||||
|
||||
// Close closes the statement.
|
||||
func (s *Stmt) Close() error {
|
||||
defer s.request.Reset()
|
||||
defer s.response.Reset()
|
||||
|
||||
protocol.EncodeFinalize(s.request, s.db, s.id)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if err := s.protocol.Call(ctx, s.request, s.response); err != nil {
|
||||
return driverError(err)
|
||||
}
|
||||
|
||||
if err := protocol.DecodeEmpty(s.response); err != nil {
|
||||
return driverError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NumInput returns the number of placeholder parameters.
|
||||
func (s *Stmt) NumInput() int {
|
||||
return int(s.params)
|
||||
}
|
||||
|
||||
// ExecContext executes a query that doesn't return rows, such
|
||||
// as an INSERT or UPDATE.
|
||||
//
|
||||
// ExecContext must honor the context timeout and return when it is canceled.
|
||||
func (s *Stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||
defer s.request.Reset()
|
||||
defer s.response.Reset()
|
||||
|
||||
protocol.EncodeExec(s.request, s.db, s.id, args)
|
||||
|
||||
if err := s.protocol.Call(ctx, s.request, s.response); err != nil {
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
result, err := protocol.DecodeResult(s.response)
|
||||
if err != nil {
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
return &Result{result: result}, nil
|
||||
}
|
||||
|
||||
// Exec executes a query that doesn't return rows, such
|
||||
func (s *Stmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
return s.ExecContext(context.Background(), valuesToNamedValues(args))
|
||||
}
|
||||
|
||||
// QueryContext executes a query that may return rows, such as a
|
||||
// SELECT.
|
||||
//
|
||||
// QueryContext must honor the context timeout and return when it is canceled.
|
||||
func (s *Stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||
defer s.request.Reset()
|
||||
|
||||
// FIXME: this shouldn't be needed but we have hit a few panics
|
||||
// probably due to the response object not being fully reset.
|
||||
s.response.Reset()
|
||||
|
||||
protocol.EncodeQuery(s.request, s.db, s.id, args)
|
||||
|
||||
if err := s.protocol.Call(ctx, s.request, s.response); err != nil {
|
||||
s.response.Reset()
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
rows, err := protocol.DecodeRows(s.response)
|
||||
if err != nil {
|
||||
s.response.Reset()
|
||||
return nil, driverError(err)
|
||||
}
|
||||
|
||||
return &Rows{ctx: ctx, request: s.request, response: s.response, protocol: s.protocol, rows: rows}, nil
|
||||
}
|
||||
|
||||
// Query executes a query that may return rows, such as a
|
||||
func (s *Stmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
return s.QueryContext(context.Background(), valuesToNamedValues(args))
|
||||
}
|
||||
|
||||
// Result is the result of a query execution.
|
||||
type Result struct {
|
||||
result protocol.Result
|
||||
}
|
||||
|
||||
// LastInsertId returns the database's auto-generated ID
|
||||
// after, for example, an INSERT into a table with primary
|
||||
// key.
|
||||
func (r *Result) LastInsertId() (int64, error) {
|
||||
return int64(r.result.LastInsertID), nil
|
||||
}
|
||||
|
||||
// RowsAffected returns the number of rows affected by the
|
||||
// query.
|
||||
func (r *Result) RowsAffected() (int64, error) {
|
||||
return int64(r.result.RowsAffected), nil
|
||||
}
|
||||
|
||||
// Rows is an iterator over an executed query's results.
|
||||
type Rows struct {
|
||||
ctx context.Context
|
||||
protocol *protocol.Protocol
|
||||
request *protocol.Message
|
||||
response *protocol.Message
|
||||
rows protocol.Rows
|
||||
consumed bool
|
||||
}
|
||||
|
||||
// Columns returns the names of the columns. The number of
|
||||
// columns of the result is inferred from the length of the
|
||||
// slice. If a particular column name isn't known, an empty
|
||||
// string should be returned for that entry.
|
||||
func (r *Rows) Columns() []string {
|
||||
return r.rows.Columns
|
||||
}
|
||||
|
||||
// Close closes the rows iterator.
|
||||
func (r *Rows) Close() error {
|
||||
err := r.rows.Close()
|
||||
|
||||
// If we consumed the whole result set, there's nothing to do as
|
||||
// there's no pending response from the server.
|
||||
if r.consumed {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If there is was a single-response result set, we're done.
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Let's issue an interrupt request and wait until we get an empty
|
||||
// response, signalling that the query was interrupted.
|
||||
if err := r.protocol.Interrupt(r.ctx, r.request, r.response); err != nil {
|
||||
return driverError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next is called to populate the next row of data into
|
||||
// the provided slice. The provided slice will be the same
|
||||
// size as the Columns() are wide.
|
||||
//
|
||||
// Next should return io.EOF when there are no more rows.
|
||||
func (r *Rows) Next(dest []driver.Value) error {
|
||||
err := r.rows.Next(dest)
|
||||
|
||||
if err == protocol.ErrRowsPart {
|
||||
r.rows.Close()
|
||||
if err := r.protocol.More(r.ctx, r.response); err != nil {
|
||||
return driverError(err)
|
||||
}
|
||||
rows, err := protocol.DecodeRows(r.response)
|
||||
if err != nil {
|
||||
return driverError(err)
|
||||
}
|
||||
r.rows = rows
|
||||
return r.rows.Next(dest)
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
r.consumed = true
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ColumnTypeScanType implements RowsColumnTypeScanType.
|
||||
func (r *Rows) ColumnTypeScanType(i int) reflect.Type {
|
||||
// column := sql.NewColumn(r.rows, i)
|
||||
|
||||
// typ, err := r.protocol.ColumnTypeScanType(context.Background(), column)
|
||||
// if err != nil {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// return typ.DriverType()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ColumnTypeDatabaseTypeName implements RowsColumnTypeDatabaseTypeName.
|
||||
func (r *Rows) ColumnTypeDatabaseTypeName(i int) string {
|
||||
// column := sql.NewColumn(r.rows, i)
|
||||
|
||||
// typeName, err := r.protocol.ColumnTypeDatabaseTypeName(context.Background(), column)
|
||||
// if err != nil {
|
||||
// return ""
|
||||
// }
|
||||
|
||||
// return typeName.Value
|
||||
return ""
|
||||
}
|
||||
|
||||
// Convert a driver.Value slice into a driver.NamedValue slice.
|
||||
func valuesToNamedValues(args []driver.Value) []driver.NamedValue {
|
||||
namedValues := make([]driver.NamedValue, len(args))
|
||||
for i, value := range args {
|
||||
namedValues[i] = driver.NamedValue{
|
||||
Ordinal: i + 1,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
return namedValues
|
||||
}
|
||||
|
||||
func driverError(err error) error {
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *net.OpError:
|
||||
return driver.ErrBadConn
|
||||
case protocol.ErrRequest:
|
||||
switch err.Code {
|
||||
case errIoErrNotLeader:
|
||||
fallthrough
|
||||
case errIoErrLeadershipLost:
|
||||
return driver.ErrBadConn
|
||||
default:
|
||||
return Error{
|
||||
Code: int(err.Code),
|
||||
Message: err.Description,
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
err := bindings.Init()
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "failed to initialize dqlite"))
|
||||
}
|
||||
}
|
||||
6
vendor/github.com/canonical/go-dqlite/internal/bindings/build.go
generated
vendored
Normal file
6
vendor/github.com/canonical/go-dqlite/internal/bindings/build.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package bindings
|
||||
|
||||
/*
|
||||
#cgo linux LDFLAGS: -lsqlite3 -lraft -lco -ldqlite
|
||||
*/
|
||||
import "C"
|
||||
19
vendor/github.com/canonical/go-dqlite/internal/bindings/errors.go
generated
vendored
Normal file
19
vendor/github.com/canonical/go-dqlite/internal/bindings/errors.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package bindings
|
||||
|
||||
/*
|
||||
#include <sqlite3.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Error holds information about a SQLite error.
|
||||
type Error struct {
|
||||
Code int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
if e.Message != "" {
|
||||
return e.Message
|
||||
}
|
||||
return C.GoString(C.sqlite3_errstr(C.int(e.Code)))
|
||||
}
|
||||
252
vendor/github.com/canonical/go-dqlite/internal/bindings/server.go
generated
vendored
Normal file
252
vendor/github.com/canonical/go-dqlite/internal/bindings/server.go
generated
vendored
Normal file
@@ -0,0 +1,252 @@
|
||||
package bindings
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <dqlite.h>
|
||||
#include <raft.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#define EMIT_BUF_LEN 1024
|
||||
|
||||
typedef unsigned long long nanoseconds_t;
|
||||
|
||||
// Duplicate a file descriptor and prevent it from being cloned into child processes.
|
||||
static int dupCloexec(int oldfd) {
|
||||
int newfd = -1;
|
||||
|
||||
newfd = dup(oldfd);
|
||||
if (newfd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fcntl(newfd, F_SETFD, FD_CLOEXEC) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return newfd;
|
||||
}
|
||||
|
||||
// C to Go trampoline for custom connect function.
|
||||
int connectWithDial(uintptr_t handle, char *address, int *fd);
|
||||
|
||||
// Wrapper to call the Go trampoline.
|
||||
static int connectTrampoline(void *data, const char *address, int *fd) {
|
||||
uintptr_t handle = (uintptr_t)(data);
|
||||
return connectWithDial(handle, (char*)address, fd);
|
||||
}
|
||||
|
||||
// Configure a custom connect function.
|
||||
static int configConnectFunc(dqlite_node *t, uintptr_t handle) {
|
||||
return dqlite_node_set_connect_func(t, connectTrampoline, (void*)handle);
|
||||
}
|
||||
|
||||
static int initializeSQLite()
|
||||
{
|
||||
int rc;
|
||||
|
||||
// Configure SQLite for single-thread mode. This is a global config.
|
||||
rc = sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
|
||||
if (rc != SQLITE_OK) {
|
||||
assert(rc == SQLITE_MISUSE);
|
||||
return DQLITE_MISUSE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static dqlite_node_info *makeInfos(int n) {
|
||||
return calloc(n, sizeof(dqlite_node_info));
|
||||
}
|
||||
|
||||
static void setInfo(dqlite_node_info *infos, unsigned i, unsigned id, const char *address) {
|
||||
dqlite_node_info *info = &infos[i];
|
||||
info->id = id;
|
||||
info->address = address;
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/canonical/go-dqlite/internal/protocol"
|
||||
)
|
||||
|
||||
type Node C.dqlite_node
|
||||
|
||||
// Init initializes dqlite global state.
|
||||
func Init() error {
|
||||
// FIXME: ignore SIGPIPE, see https://github.com/joyent/libuv/issues/1254
|
||||
C.signal(C.SIGPIPE, C.SIG_IGN)
|
||||
// Don't enable single thread mode when running tests. TODO: find a
|
||||
// better way to expose this functionality.
|
||||
if os.Getenv("GO_DQLITE_MULTITHREAD") == "1" {
|
||||
return nil
|
||||
}
|
||||
if rc := C.initializeSQLite(); rc != 0 {
|
||||
return fmt.Errorf("%d", rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewNode creates a new Node instance.
|
||||
func NewNode(id uint64, address string, dir string) (*Node, error) {
|
||||
var server *C.dqlite_node
|
||||
cid := C.unsigned(id)
|
||||
|
||||
caddress := C.CString(address)
|
||||
defer C.free(unsafe.Pointer(caddress))
|
||||
|
||||
cdir := C.CString(dir)
|
||||
defer C.free(unsafe.Pointer(cdir))
|
||||
|
||||
if rc := C.dqlite_node_create(cid, caddress, cdir, &server); rc != 0 {
|
||||
return nil, fmt.Errorf("failed to create task object")
|
||||
}
|
||||
|
||||
return (*Node)(unsafe.Pointer(server)), nil
|
||||
}
|
||||
|
||||
func (s *Node) SetDialFunc(dial protocol.DialFunc) error {
|
||||
server := (*C.dqlite_node)(unsafe.Pointer(s))
|
||||
connectLock.Lock()
|
||||
defer connectLock.Unlock()
|
||||
connectIndex++
|
||||
connectRegistry[connectIndex] = dial
|
||||
if rc := C.configConnectFunc(server, connectIndex); rc != 0 {
|
||||
return fmt.Errorf("failed to set connect func")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Node) SetBindAddress(address string) error {
|
||||
server := (*C.dqlite_node)(unsafe.Pointer(s))
|
||||
caddress := C.CString(address)
|
||||
defer C.free(unsafe.Pointer(caddress))
|
||||
if rc := C.dqlite_node_set_bind_address(server, caddress); rc != 0 {
|
||||
return fmt.Errorf("failed to set bind address")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Node) SetNetworkLatency(nanoseconds uint64) error {
|
||||
server := (*C.dqlite_node)(unsafe.Pointer(s))
|
||||
cnanoseconds := C.nanoseconds_t(nanoseconds)
|
||||
if rc := C.dqlite_node_set_network_latency(server, cnanoseconds); rc != 0 {
|
||||
return fmt.Errorf("failed to set network latency")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Node) GetBindAddress() string {
|
||||
server := (*C.dqlite_node)(unsafe.Pointer(s))
|
||||
return C.GoString(C.dqlite_node_get_bind_address(server))
|
||||
}
|
||||
|
||||
func (s *Node) Start() error {
|
||||
server := (*C.dqlite_node)(unsafe.Pointer(s))
|
||||
if rc := C.dqlite_node_start(server); rc != 0 {
|
||||
return fmt.Errorf("failed to start task")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Node) Stop() error {
|
||||
server := (*C.dqlite_node)(unsafe.Pointer(s))
|
||||
if rc := C.dqlite_node_stop(server); rc != 0 {
|
||||
return fmt.Errorf("task stopped with error code %d", rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the server releasing all used resources.
|
||||
func (s *Node) Close() {
|
||||
server := (*C.dqlite_node)(unsafe.Pointer(s))
|
||||
C.dqlite_node_destroy(server)
|
||||
}
|
||||
|
||||
func (s *Node) Recover(cluster []protocol.NodeInfo) error {
|
||||
server := (*C.dqlite_node)(unsafe.Pointer(s))
|
||||
n := C.int(len(cluster))
|
||||
infos := C.makeInfos(n)
|
||||
defer C.free(unsafe.Pointer(infos))
|
||||
for i, info := range cluster {
|
||||
cid := C.unsigned(info.ID)
|
||||
caddress := C.CString(info.Address)
|
||||
defer C.free(unsafe.Pointer(caddress))
|
||||
C.setInfo(infos, C.unsigned(i), cid, caddress)
|
||||
}
|
||||
if rc := C.dqlite_node_recover(server, infos, n); rc != 0 {
|
||||
return fmt.Errorf("recover failed with error code %d", rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extract the underlying socket from a connection.
|
||||
func connToSocket(conn net.Conn) (C.int, error) {
|
||||
file, err := conn.(fileConn).File()
|
||||
if err != nil {
|
||||
return C.int(-1), err
|
||||
}
|
||||
|
||||
fd1 := C.int(file.Fd())
|
||||
|
||||
// Duplicate the file descriptor, in order to prevent Go's finalizer to
|
||||
// close it.
|
||||
fd2 := C.dupCloexec(fd1)
|
||||
if fd2 < 0 {
|
||||
return C.int(-1), fmt.Errorf("failed to dup socket fd")
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
|
||||
return fd2, nil
|
||||
}
|
||||
|
||||
// Interface that net.Conn must implement in order to extract the underlying
|
||||
// file descriptor.
|
||||
type fileConn interface {
|
||||
File() (*os.File, error)
|
||||
}
|
||||
|
||||
//export connectWithDial
|
||||
func connectWithDial(handle C.uintptr_t, address *C.char, fd *C.int) C.int {
|
||||
connectLock.Lock()
|
||||
defer connectLock.Unlock()
|
||||
dial := connectRegistry[handle]
|
||||
// TODO: make timeout customizable.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
conn, err := dial(ctx, C.GoString(address))
|
||||
if err != nil {
|
||||
return C.RAFT_NOCONNECTION
|
||||
}
|
||||
socket, err := connToSocket(conn)
|
||||
if err != nil {
|
||||
return C.RAFT_NOCONNECTION
|
||||
}
|
||||
*fd = socket
|
||||
return C.int(0)
|
||||
}
|
||||
|
||||
// Use handles to avoid passing Go pointers to C.
|
||||
var connectRegistry = make(map[C.uintptr_t]protocol.DialFunc)
|
||||
var connectIndex C.uintptr_t = 100
|
||||
var connectLock = sync.Mutex{}
|
||||
|
||||
// ErrNodeStopped is returned by Node.Handle() is the server was stopped.
|
||||
var ErrNodeStopped = fmt.Errorf("server was stopped")
|
||||
|
||||
// To compare bool values.
|
||||
var cfalse C.bool
|
||||
26
vendor/github.com/canonical/go-dqlite/internal/logging/func.go
generated
vendored
Normal file
26
vendor/github.com/canonical/go-dqlite/internal/logging/func.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Func is a function that can be used for logging.
|
||||
type Func func(Level, string, ...interface{})
|
||||
|
||||
// Test returns a logging function that forwards messages to the test logger.
|
||||
func Test(t *testing.T) Func {
|
||||
return func(l Level, format string, a ...interface{}) {
|
||||
format = fmt.Sprintf("%s: %s", l.String(), format)
|
||||
t.Logf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
// Stdout returns a logging function that prints log messages on standard
|
||||
// output.
|
||||
func Stdout() Func {
|
||||
return func(l Level, format string, a ...interface{}) {
|
||||
format = fmt.Sprintf("%s: %s\n", l.String(), format)
|
||||
fmt.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
27
vendor/github.com/canonical/go-dqlite/internal/logging/level.go
generated
vendored
Normal file
27
vendor/github.com/canonical/go-dqlite/internal/logging/level.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package logging
|
||||
|
||||
// Level defines the logging level.
|
||||
type Level int
|
||||
|
||||
// Available logging levels.
|
||||
const (
|
||||
Debug Level = iota
|
||||
Info
|
||||
Warn
|
||||
Error
|
||||
)
|
||||
|
||||
func (l Level) String() string {
|
||||
switch l {
|
||||
case Debug:
|
||||
return "DEBUG"
|
||||
case Info:
|
||||
return "INFO"
|
||||
case Warn:
|
||||
return "WARN"
|
||||
case Error:
|
||||
return "ERROR"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
11
vendor/github.com/canonical/go-dqlite/internal/protocol/buffer.go
generated
vendored
Normal file
11
vendor/github.com/canonical/go-dqlite/internal/protocol/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
package protocol
|
||||
|
||||
// Buffer for reading responses or writing requests.
|
||||
type buffer struct {
|
||||
Bytes []byte
|
||||
Offset int
|
||||
}
|
||||
|
||||
func (b *buffer) Advance(amount int) {
|
||||
b.Offset += amount
|
||||
}
|
||||
14
vendor/github.com/canonical/go-dqlite/internal/protocol/config.go
generated
vendored
Normal file
14
vendor/github.com/canonical/go-dqlite/internal/protocol/config.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Rican7/retry/strategy"
|
||||
)
|
||||
|
||||
// Config holds various configuration parameters for a dqlite client.
|
||||
type Config struct {
|
||||
Dial DialFunc // Network dialer.
|
||||
AttemptTimeout time.Duration // Timeout for each individual Dial attempt.
|
||||
RetryStrategies []strategy.Strategy // Strategies used for retrying to connect to a leader.
|
||||
}
|
||||
250
vendor/github.com/canonical/go-dqlite/internal/protocol/connector.go
generated
vendored
Normal file
250
vendor/github.com/canonical/go-dqlite/internal/protocol/connector.go
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/Rican7/retry"
|
||||
"github.com/canonical/go-dqlite/internal/logging"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DialFunc is a function that can be used to establish a network connection.
|
||||
type DialFunc func(context.Context, string) (net.Conn, error)
|
||||
|
||||
// Connector is in charge of creating a dqlite SQL client connected to the
|
||||
// current leader of a cluster.
|
||||
type Connector struct {
|
||||
id uint64 // Conn ID to use when registering against the server.
|
||||
store NodeStore // Used to get and update current cluster servers.
|
||||
config Config // Connection parameters.
|
||||
log logging.Func // Logging function.
|
||||
}
|
||||
|
||||
// NewConnector returns a new connector that can be used by a dqlite driver to
|
||||
// create new clients connected to a leader dqlite server.
|
||||
func NewConnector(id uint64, store NodeStore, config Config, log logging.Func) *Connector {
|
||||
connector := &Connector{
|
||||
id: id,
|
||||
store: store,
|
||||
config: config,
|
||||
log: log,
|
||||
}
|
||||
|
||||
return connector
|
||||
}
|
||||
|
||||
// Connect finds the leader server and returns a connection to it.
|
||||
//
|
||||
// If the connector is stopped before a leader is found, nil is returned.
|
||||
func (c *Connector) Connect(ctx context.Context) (*Protocol, error) {
|
||||
var protocol *Protocol
|
||||
|
||||
// The retry strategy should be configured to retry indefinitely, until
|
||||
// the given context is done.
|
||||
err := retry.Retry(func(attempt uint) error {
|
||||
log := func(l logging.Level, format string, a ...interface{}) {
|
||||
format += fmt.Sprintf(" attempt=%d", attempt)
|
||||
c.log(l, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Stop retrying
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
var err error
|
||||
protocol, err = c.connectAttemptAll(ctx, log)
|
||||
if err != nil {
|
||||
log(logging.Debug, "connection failed err=%v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}, c.config.RetryStrategies...)
|
||||
|
||||
if err != nil {
|
||||
// The retry strategy should never give up until success or
|
||||
// context expiration.
|
||||
panic("connect retry aborted unexpectedly")
|
||||
}
|
||||
|
||||
if ctx.Err() != nil {
|
||||
return nil, ErrNoAvailableLeader
|
||||
}
|
||||
|
||||
return protocol, nil
|
||||
}
|
||||
|
||||
// Make a single attempt to establish a connection to the leader server trying
|
||||
// all addresses available in the store.
|
||||
func (c *Connector) connectAttemptAll(ctx context.Context, log logging.Func) (*Protocol, error) {
|
||||
servers, err := c.store.Get(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get cluster servers")
|
||||
}
|
||||
|
||||
// Make an attempt for each address until we find the leader.
|
||||
for _, server := range servers {
|
||||
log := func(l logging.Level, format string, a ...interface{}) {
|
||||
format += fmt.Sprintf(" address=%s", server.Address)
|
||||
log(l, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, c.config.AttemptTimeout)
|
||||
defer cancel()
|
||||
|
||||
version := VersionOne
|
||||
protocol, leader, err := c.connectAttemptOne(ctx, server.Address, version)
|
||||
if err == errBadProtocol {
|
||||
version = VersionLegacy
|
||||
protocol, leader, err = c.connectAttemptOne(ctx, server.Address, version)
|
||||
}
|
||||
if err != nil {
|
||||
// This server is unavailable, try with the next target.
|
||||
log(logging.Debug, "server connection failed err=%v", err)
|
||||
continue
|
||||
}
|
||||
if protocol != nil {
|
||||
// We found the leader
|
||||
log(logging.Info, "connected")
|
||||
return protocol, nil
|
||||
}
|
||||
if leader == "" {
|
||||
// This server does not know who the current leader is,
|
||||
// try with the next target.
|
||||
continue
|
||||
}
|
||||
|
||||
// If we get here, it means this server reported that another
|
||||
// server is the leader, let's close the connection to this
|
||||
// server and try with the suggested one.
|
||||
//logger = logger.With(zap.String("leader", leader))
|
||||
protocol, leader, err = c.connectAttemptOne(ctx, leader, version)
|
||||
if err != nil {
|
||||
// The leader reported by the previous server is
|
||||
// unavailable, try with the next target.
|
||||
//logger.Info("leader server connection failed", zap.String("err", err.Error()))
|
||||
continue
|
||||
}
|
||||
if protocol == nil {
|
||||
// The leader reported by the target server does not consider itself
|
||||
// the leader, try with the next target.
|
||||
//logger.Info("reported leader server is not the leader")
|
||||
continue
|
||||
}
|
||||
log(logging.Info, "connected")
|
||||
return protocol, nil
|
||||
}
|
||||
|
||||
return nil, ErrNoAvailableLeader
|
||||
}
|
||||
|
||||
// Connect establishes a connection with a dqlite node.
|
||||
func Connect(ctx context.Context, dial DialFunc, address string, version uint64) (*Protocol, error) {
|
||||
// Establish the connection.
|
||||
conn, err := dial(ctx, address)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to establish network connection")
|
||||
}
|
||||
|
||||
// Latest protocol version.
|
||||
protocol := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(protocol, version)
|
||||
|
||||
// Perform the protocol handshake.
|
||||
n, err := conn.Write(protocol)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, errors.Wrap(err, "failed to send handshake")
|
||||
}
|
||||
if n != 8 {
|
||||
conn.Close()
|
||||
return nil, errors.Wrap(io.ErrShortWrite, "failed to send handshake")
|
||||
}
|
||||
|
||||
return NewProtocol(version, conn), nil
|
||||
}
|
||||
|
||||
// Connect to the given dqlite server and check if it's the leader.
|
||||
//
|
||||
// Return values:
|
||||
//
|
||||
// - Any failure is hit: -> nil, "", err
|
||||
// - Target not leader and no leader known: -> nil, "", nil
|
||||
// - Target not leader and leader known: -> nil, leader, nil
|
||||
// - Target is the leader: -> server, "", nil
|
||||
//
|
||||
func (c *Connector) connectAttemptOne(ctx context.Context, address string, version uint64) (*Protocol, string, error) {
|
||||
protocol, err := Connect(ctx, c.config.Dial, address, version)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Send the initial Leader request.
|
||||
request := Message{}
|
||||
request.Init(16)
|
||||
response := Message{}
|
||||
response.Init(512)
|
||||
|
||||
EncodeLeader(&request)
|
||||
|
||||
if err := protocol.Call(ctx, &request, &response); err != nil {
|
||||
protocol.Close()
|
||||
cause := errors.Cause(err)
|
||||
// Best-effort detection of a pre-1.0 dqlite node: when sent
|
||||
// version 1 it should close the connection immediately.
|
||||
if _, ok := cause.(*net.OpError); ok || cause == io.EOF {
|
||||
return nil, "", errBadProtocol
|
||||
}
|
||||
|
||||
return nil, "", errors.Wrap(err, "failed to send Leader request")
|
||||
}
|
||||
|
||||
_, leader, err := DecodeNodeCompat(protocol, &response)
|
||||
if err != nil {
|
||||
protocol.Close()
|
||||
return nil, "", errors.Wrap(err, "failed to parse Node response")
|
||||
}
|
||||
|
||||
switch leader {
|
||||
case "":
|
||||
// Currently this server does not know about any leader.
|
||||
protocol.Close()
|
||||
return nil, "", nil
|
||||
case address:
|
||||
// This server is the leader, register ourselves and return.
|
||||
request.Reset()
|
||||
response.Reset()
|
||||
|
||||
EncodeClient(&request, c.id)
|
||||
|
||||
if err := protocol.Call(ctx, &request, &response); err != nil {
|
||||
protocol.Close()
|
||||
return nil, "", errors.Wrap(err, "failed to send Conn request")
|
||||
}
|
||||
|
||||
_, err := DecodeWelcome(&response)
|
||||
if err != nil {
|
||||
protocol.Close()
|
||||
return nil, "", errors.Wrap(err, "failed to parse Welcome response")
|
||||
}
|
||||
|
||||
// TODO: enable heartbeat
|
||||
// protocol.heartbeatTimeout = time.Duration(heartbeatTimeout) * time.Millisecond
|
||||
//go protocol.heartbeat()
|
||||
|
||||
return protocol, "", nil
|
||||
default:
|
||||
// This server claims to know who the current leader is.
|
||||
protocol.Close()
|
||||
return nil, leader, nil
|
||||
}
|
||||
}
|
||||
|
||||
var errBadProtocol = fmt.Errorf("bad protocol")
|
||||
58
vendor/github.com/canonical/go-dqlite/internal/protocol/constants.go
generated
vendored
Normal file
58
vendor/github.com/canonical/go-dqlite/internal/protocol/constants.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package protocol
|
||||
|
||||
// VersionOne is version 1 of the server protocol.
|
||||
const VersionOne = uint64(1)
|
||||
|
||||
// VersionLegacy is the pre 1.0 dqlite server protocol version.
|
||||
const VersionLegacy = uint64(0x86104dd760433fe5)
|
||||
|
||||
// SQLite datatype codes
|
||||
const (
|
||||
Integer = 1
|
||||
Float = 2
|
||||
Text = 3
|
||||
Blob = 4
|
||||
Null = 5
|
||||
)
|
||||
|
||||
// Special data types for time values.
|
||||
const (
|
||||
UnixTime = 9
|
||||
ISO8601 = 10
|
||||
Boolean = 11
|
||||
)
|
||||
|
||||
// Request types.
|
||||
const (
|
||||
RequestLeader = 0
|
||||
RequestClient = 1
|
||||
RequestHeartbeat = 2
|
||||
RequestOpen = 3
|
||||
RequestPrepare = 4
|
||||
RequestExec = 5
|
||||
RequestQuery = 6
|
||||
RequestFinalize = 7
|
||||
RequestExecSQL = 8
|
||||
RequestQuerySQL = 9
|
||||
RequestInterrupt = 10
|
||||
RequestJoin = 12
|
||||
RequestPromote = 13
|
||||
RequestRemove = 14
|
||||
RequestDump = 15
|
||||
RequestCluster = 16
|
||||
)
|
||||
|
||||
// Response types.
|
||||
const (
|
||||
ResponseFailure = 0
|
||||
ResponseNode = 1
|
||||
ResponseNodeLegacy = 1
|
||||
ResponseWelcome = 2
|
||||
ResponseNodes = 3
|
||||
ResponseDb = 4
|
||||
ResponseStmt = 5
|
||||
ResponseResult = 6
|
||||
ResponseRows = 7
|
||||
ResponseEmpty = 8
|
||||
ResponseFiles = 9
|
||||
)
|
||||
20
vendor/github.com/canonical/go-dqlite/internal/protocol/dial.go
generated
vendored
Normal file
20
vendor/github.com/canonical/go-dqlite/internal/protocol/dial.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
// TCPDial is a dial function using plain TCP to establish the network
|
||||
// connection.
|
||||
func TCPDial(ctx context.Context, address string) (net.Conn, error) {
|
||||
dialer := net.Dialer{}
|
||||
return dialer.DialContext(ctx, "tcp", address)
|
||||
}
|
||||
|
||||
// UnixDial is a dial function using Unix sockets to establish the network
|
||||
// connection.
|
||||
func UnixDial(ctx context.Context, address string) (net.Conn, error) {
|
||||
dialer := net.Dialer{}
|
||||
return dialer.DialContext(ctx, "unix", address)
|
||||
}
|
||||
29
vendor/github.com/canonical/go-dqlite/internal/protocol/errors.go
generated
vendored
Normal file
29
vendor/github.com/canonical/go-dqlite/internal/protocol/errors.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Client errors.
|
||||
var (
|
||||
ErrNoAvailableLeader = fmt.Errorf("no available dqlite leader server found")
|
||||
errStop = fmt.Errorf("connector was stopped")
|
||||
errStaleLeader = fmt.Errorf("server has lost leadership")
|
||||
errNotClustered = fmt.Errorf("server is not clustered")
|
||||
errNegativeRead = fmt.Errorf("reader returned negative count from Read")
|
||||
errMessageEOF = fmt.Errorf("message eof")
|
||||
)
|
||||
|
||||
// ErrRequest is returned in case of request failure.
|
||||
type ErrRequest struct {
|
||||
Code uint64
|
||||
Description string
|
||||
}
|
||||
|
||||
func (e ErrRequest) Error() string {
|
||||
return fmt.Sprintf("%s (%d)", e.Description, e.Code)
|
||||
}
|
||||
|
||||
// ErrRowsPart is returned when the first batch of a multi-response result
|
||||
// batch is done.
|
||||
var ErrRowsPart = fmt.Errorf("not all rows were returned in this response")
|
||||
660
vendor/github.com/canonical/go-dqlite/internal/protocol/message.go
generated
vendored
Normal file
660
vendor/github.com/canonical/go-dqlite/internal/protocol/message.go
generated
vendored
Normal file
@@ -0,0 +1,660 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NamedValues is a type alias of a slice of driver.NamedValue. It's used by
|
||||
// schema.sh to generate encoding logic for statement parameters.
|
||||
type NamedValues = []driver.NamedValue
|
||||
|
||||
// Nodes is a type alias of a slice of NodeInfo. It's used by schema.sh to
|
||||
// generate decoding logic for the heartbeat response.
|
||||
type Nodes []NodeInfo
|
||||
|
||||
// Message holds data about a single request or response.
|
||||
type Message struct {
|
||||
words uint32
|
||||
mtype uint8
|
||||
flags uint8
|
||||
extra uint16
|
||||
header []byte // Statically allocated header buffer
|
||||
body1 buffer // Statically allocated body data, using bytes
|
||||
body2 buffer // Dynamically allocated body data
|
||||
}
|
||||
|
||||
// Init initializes the message using the given size of the statically
|
||||
// allocated buffer (i.e. a buffer which is re-used across requests or
|
||||
// responses encoded or decoded using this message object).
|
||||
func (m *Message) Init(staticSize int) {
|
||||
if (staticSize % messageWordSize) != 0 {
|
||||
panic("static size is not aligned to word boundary")
|
||||
}
|
||||
m.header = make([]byte, messageHeaderSize)
|
||||
m.body1.Bytes = make([]byte, staticSize)
|
||||
m.Reset()
|
||||
}
|
||||
|
||||
// Reset the state of the message so it can be used to encode or decode again.
|
||||
func (m *Message) Reset() {
|
||||
m.words = 0
|
||||
m.mtype = 0
|
||||
m.flags = 0
|
||||
m.extra = 0
|
||||
for i := 0; i < messageHeaderSize; i++ {
|
||||
m.header[i] = 0
|
||||
}
|
||||
m.body1.Offset = 0
|
||||
m.body2.Bytes = nil
|
||||
m.body2.Offset = 0
|
||||
}
|
||||
|
||||
// Append a byte slice to the message.
|
||||
func (m *Message) putBlob(v []byte) {
|
||||
size := len(v)
|
||||
m.putUint64(uint64(size))
|
||||
|
||||
pad := 0
|
||||
if (size % messageWordSize) != 0 {
|
||||
// Account for padding
|
||||
pad = messageWordSize - (size % messageWordSize)
|
||||
size += pad
|
||||
}
|
||||
|
||||
b := m.bufferForPut(size)
|
||||
defer b.Advance(size)
|
||||
|
||||
// Copy the bytes into the buffer.
|
||||
offset := b.Offset
|
||||
copy(b.Bytes[offset:], v)
|
||||
offset += len(v)
|
||||
|
||||
// Add padding
|
||||
for i := 0; i < pad; i++ {
|
||||
b.Bytes[offset] = 0
|
||||
offset++
|
||||
}
|
||||
}
|
||||
|
||||
// Append a string to the message.
|
||||
func (m *Message) putString(v string) {
|
||||
size := len(v) + 1
|
||||
pad := 0
|
||||
if (size % messageWordSize) != 0 {
|
||||
// Account for padding
|
||||
pad = messageWordSize - (size % messageWordSize)
|
||||
size += pad
|
||||
}
|
||||
|
||||
b := m.bufferForPut(size)
|
||||
defer b.Advance(size)
|
||||
|
||||
// Copy the string bytes into the buffer.
|
||||
offset := b.Offset
|
||||
copy(b.Bytes[offset:], v)
|
||||
offset += len(v)
|
||||
|
||||
// Add a nul byte
|
||||
b.Bytes[offset] = 0
|
||||
offset++
|
||||
|
||||
// Add padding
|
||||
for i := 0; i < pad; i++ {
|
||||
b.Bytes[offset] = 0
|
||||
offset++
|
||||
}
|
||||
}
|
||||
|
||||
// Append a byte to the message.
|
||||
func (m *Message) putUint8(v uint8) {
|
||||
b := m.bufferForPut(1)
|
||||
defer b.Advance(1)
|
||||
|
||||
b.Bytes[b.Offset] = v
|
||||
}
|
||||
|
||||
// Append a 2-byte word to the message.
|
||||
func (m *Message) putUint16(v uint16) {
|
||||
b := m.bufferForPut(2)
|
||||
defer b.Advance(2)
|
||||
|
||||
binary.LittleEndian.PutUint16(b.Bytes[b.Offset:], v)
|
||||
}
|
||||
|
||||
// Append a 4-byte word to the message.
|
||||
func (m *Message) putUint32(v uint32) {
|
||||
b := m.bufferForPut(4)
|
||||
defer b.Advance(4)
|
||||
|
||||
binary.LittleEndian.PutUint32(b.Bytes[b.Offset:], v)
|
||||
}
|
||||
|
||||
// Append an 8-byte word to the message.
|
||||
func (m *Message) putUint64(v uint64) {
|
||||
b := m.bufferForPut(8)
|
||||
defer b.Advance(8)
|
||||
|
||||
binary.LittleEndian.PutUint64(b.Bytes[b.Offset:], v)
|
||||
}
|
||||
|
||||
// Append a signed 8-byte word to the message.
|
||||
func (m *Message) putInt64(v int64) {
|
||||
b := m.bufferForPut(8)
|
||||
defer b.Advance(8)
|
||||
|
||||
binary.LittleEndian.PutUint64(b.Bytes[b.Offset:], uint64(v))
|
||||
}
|
||||
|
||||
// Append a floating point number to the message.
|
||||
func (m *Message) putFloat64(v float64) {
|
||||
b := m.bufferForPut(8)
|
||||
defer b.Advance(8)
|
||||
|
||||
binary.LittleEndian.PutUint64(b.Bytes[b.Offset:], math.Float64bits(v))
|
||||
}
|
||||
|
||||
// Encode the given driver values as binding parameters.
|
||||
func (m *Message) putNamedValues(values NamedValues) {
|
||||
n := uint8(len(values)) // N of params
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
m.putUint8(n)
|
||||
|
||||
for i := range values {
|
||||
if values[i].Ordinal != i+1 {
|
||||
panic("unexpected ordinal")
|
||||
}
|
||||
|
||||
switch values[i].Value.(type) {
|
||||
case int64:
|
||||
m.putUint8(Integer)
|
||||
case float64:
|
||||
m.putUint8(Float)
|
||||
case bool:
|
||||
m.putUint8(Boolean)
|
||||
case []byte:
|
||||
m.putUint8(Blob)
|
||||
case string:
|
||||
m.putUint8(Text)
|
||||
case nil:
|
||||
m.putUint8(Null)
|
||||
case time.Time:
|
||||
m.putUint8(ISO8601)
|
||||
default:
|
||||
panic("unsupported value type")
|
||||
}
|
||||
}
|
||||
|
||||
b := m.bufferForPut(1)
|
||||
|
||||
if trailing := b.Offset % messageWordSize; trailing != 0 {
|
||||
// Skip padding bytes
|
||||
b.Advance(messageWordSize - trailing)
|
||||
}
|
||||
|
||||
for i := range values {
|
||||
switch v := values[i].Value.(type) {
|
||||
case int64:
|
||||
m.putInt64(v)
|
||||
case float64:
|
||||
m.putFloat64(v)
|
||||
case bool:
|
||||
if v {
|
||||
m.putUint64(1)
|
||||
} else {
|
||||
m.putUint64(0)
|
||||
}
|
||||
case []byte:
|
||||
m.putBlob(v)
|
||||
case string:
|
||||
m.putString(v)
|
||||
case nil:
|
||||
m.putInt64(0)
|
||||
case time.Time:
|
||||
timestamp := v.Format(iso8601Formats[0])
|
||||
m.putString(timestamp)
|
||||
default:
|
||||
panic("unsupported value type")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Finalize the message by setting the message type and the number
|
||||
// of words in the body (calculated from the body size).
|
||||
func (m *Message) putHeader(mtype uint8) {
|
||||
if m.body1.Offset <= 0 {
|
||||
panic("static offset is not positive")
|
||||
}
|
||||
|
||||
if (m.body1.Offset % messageWordSize) != 0 {
|
||||
panic("static body is not aligned")
|
||||
}
|
||||
|
||||
m.mtype = mtype
|
||||
m.flags = 0
|
||||
m.extra = 0
|
||||
|
||||
m.words = uint32(m.body1.Offset) / messageWordSize
|
||||
|
||||
if m.body2.Bytes == nil {
|
||||
m.finalize()
|
||||
return
|
||||
}
|
||||
|
||||
if m.body2.Offset <= 0 {
|
||||
panic("dynamic offset is not positive")
|
||||
}
|
||||
|
||||
if (m.body2.Offset % messageWordSize) != 0 {
|
||||
panic("dynamic body is not aligned")
|
||||
}
|
||||
|
||||
m.words += uint32(m.body2.Offset) / messageWordSize
|
||||
|
||||
m.finalize()
|
||||
}
|
||||
|
||||
func (m *Message) finalize() {
|
||||
if m.words == 0 {
|
||||
panic("empty message body")
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(m.header[0:], m.words)
|
||||
m.header[4] = m.mtype
|
||||
m.header[5] = m.flags
|
||||
binary.LittleEndian.PutUint16(m.header[6:], m.extra)
|
||||
}
|
||||
|
||||
func (m *Message) bufferForPut(size int) *buffer {
|
||||
if m.body2.Bytes != nil {
|
||||
if (m.body2.Offset + size) > len(m.body2.Bytes) {
|
||||
// Grow body2.
|
||||
//
|
||||
// TODO: find a good grow strategy.
|
||||
bytes := make([]byte, m.body2.Offset+size)
|
||||
copy(bytes, m.body2.Bytes)
|
||||
m.body2.Bytes = bytes
|
||||
}
|
||||
|
||||
return &m.body2
|
||||
}
|
||||
|
||||
if (m.body1.Offset + size) > len(m.body1.Bytes) {
|
||||
m.body2.Bytes = make([]byte, size)
|
||||
m.body2.Offset = 0
|
||||
|
||||
return &m.body2
|
||||
}
|
||||
|
||||
return &m.body1
|
||||
}
|
||||
|
||||
// Return the message type and its flags.
|
||||
func (m *Message) getHeader() (uint8, uint8) {
|
||||
return m.mtype, m.flags
|
||||
}
|
||||
|
||||
// Read a string from the message body.
|
||||
func (m *Message) getString() string {
|
||||
b := m.bufferForGet()
|
||||
|
||||
index := bytes.IndexByte(b.Bytes[b.Offset:], 0)
|
||||
if index == -1 {
|
||||
// Check if the string overflows in the dynamic buffer.
|
||||
if b == &m.body1 && m.body2.Bytes != nil {
|
||||
// Assert that this is the first read of the dynamic buffer.
|
||||
if m.body2.Offset != 0 {
|
||||
panic("static buffer read after dynamic buffer one")
|
||||
}
|
||||
index = bytes.IndexByte(m.body2.Bytes[0:], 0)
|
||||
if index != -1 {
|
||||
// We found the trailing part of the string.
|
||||
data := b.Bytes[b.Offset:]
|
||||
data = append(data, m.body2.Bytes[0:index]...)
|
||||
|
||||
index++
|
||||
|
||||
if trailing := index % messageWordSize; trailing != 0 {
|
||||
// Account for padding, moving index to the next word boundary.
|
||||
index += messageWordSize - trailing
|
||||
}
|
||||
|
||||
m.body1.Offset = len(m.body1.Bytes)
|
||||
m.body2.Advance(index)
|
||||
|
||||
return string(data)
|
||||
}
|
||||
}
|
||||
panic("no string found")
|
||||
}
|
||||
s := string(b.Bytes[b.Offset : b.Offset+index])
|
||||
|
||||
index++
|
||||
|
||||
if trailing := index % messageWordSize; trailing != 0 {
|
||||
// Account for padding, moving index to the next word boundary.
|
||||
index += messageWordSize - trailing
|
||||
}
|
||||
|
||||
b.Advance(index)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (m *Message) getBlob() []byte {
|
||||
size := m.getUint64()
|
||||
data := make([]byte, size)
|
||||
for i := range data {
|
||||
data[i] = m.getUint8()
|
||||
}
|
||||
pad := 0
|
||||
if (size % messageWordSize) != 0 {
|
||||
// Account for padding
|
||||
pad = int(messageWordSize - (size % messageWordSize))
|
||||
}
|
||||
// Consume padding
|
||||
for i := 0; i < pad; i++ {
|
||||
m.getUint8()
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Read a byte from the message body.
|
||||
func (m *Message) getUint8() uint8 {
|
||||
b := m.bufferForGet()
|
||||
defer b.Advance(1)
|
||||
|
||||
return b.Bytes[b.Offset]
|
||||
}
|
||||
|
||||
// Read a 2-byte word from the message body.
|
||||
func (m *Message) getUint16() uint16 {
|
||||
b := m.bufferForGet()
|
||||
defer b.Advance(2)
|
||||
|
||||
return binary.LittleEndian.Uint16(b.Bytes[b.Offset:])
|
||||
}
|
||||
|
||||
// Read a 4-byte word from the message body.
|
||||
func (m *Message) getUint32() uint32 {
|
||||
b := m.bufferForGet()
|
||||
defer b.Advance(4)
|
||||
|
||||
return binary.LittleEndian.Uint32(b.Bytes[b.Offset:])
|
||||
}
|
||||
|
||||
// Read reads an 8-byte word from the message body.
|
||||
func (m *Message) getUint64() uint64 {
|
||||
b := m.bufferForGet()
|
||||
defer b.Advance(8)
|
||||
|
||||
return binary.LittleEndian.Uint64(b.Bytes[b.Offset:])
|
||||
}
|
||||
|
||||
// Read a signed 8-byte word from the message body.
|
||||
func (m *Message) getInt64() int64 {
|
||||
b := m.bufferForGet()
|
||||
defer b.Advance(8)
|
||||
|
||||
return int64(binary.LittleEndian.Uint64(b.Bytes[b.Offset:]))
|
||||
}
|
||||
|
||||
// Read a floating point number from the message body.
|
||||
func (m *Message) getFloat64() float64 {
|
||||
b := m.bufferForGet()
|
||||
defer b.Advance(8)
|
||||
|
||||
return math.Float64frombits(binary.LittleEndian.Uint64(b.Bytes[b.Offset:]))
|
||||
}
|
||||
|
||||
// Decode a list of server objects from the message body.
|
||||
func (m *Message) getNodes() Nodes {
|
||||
n := m.getUint64()
|
||||
servers := make(Nodes, n)
|
||||
|
||||
for i := 0; i < int(n); i++ {
|
||||
servers[i].ID = m.getUint64()
|
||||
servers[i].Address = m.getString()
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
// Decode a statement result object from the message body.
|
||||
func (m *Message) getResult() Result {
|
||||
return Result{
|
||||
LastInsertID: m.getUint64(),
|
||||
RowsAffected: m.getUint64(),
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a query result set object from the message body.
|
||||
func (m *Message) getRows() Rows {
|
||||
// Read the column count and column names.
|
||||
columns := make([]string, m.getUint64())
|
||||
|
||||
for i := range columns {
|
||||
columns[i] = m.getString()
|
||||
}
|
||||
|
||||
rows := Rows{
|
||||
Columns: columns,
|
||||
message: m,
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func (m *Message) getFiles() Files {
|
||||
files := Files{
|
||||
n: m.getUint64(),
|
||||
message: m,
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func (m *Message) hasBeenConsumed() bool {
|
||||
size := int(m.words * messageWordSize)
|
||||
return (m.body1.Offset == size || m.body1.Offset == len(m.body1.Bytes)) &&
|
||||
m.body1.Offset+m.body2.Offset == size
|
||||
}
|
||||
|
||||
func (m *Message) lastByte() byte {
|
||||
size := int(m.words * messageWordSize)
|
||||
if size > len(m.body1.Bytes) {
|
||||
size = size - m.body1.Offset
|
||||
return m.body2.Bytes[size-1]
|
||||
}
|
||||
return m.body1.Bytes[size-1]
|
||||
}
|
||||
|
||||
func (m *Message) bufferForGet() *buffer {
|
||||
size := int(m.words * messageWordSize)
|
||||
if m.body1.Offset == size || m.body1.Offset == len(m.body1.Bytes) {
|
||||
// The static body has been exahusted, use the dynamic one.
|
||||
if m.body1.Offset+m.body2.Offset == size {
|
||||
err := fmt.Errorf("short message: type=%d words=%d off=%d", m.mtype, m.words, m.body1.Offset)
|
||||
panic(err)
|
||||
}
|
||||
return &m.body2
|
||||
}
|
||||
|
||||
return &m.body1
|
||||
}
|
||||
|
||||
// Result holds the result of a statement.
|
||||
type Result struct {
|
||||
LastInsertID uint64
|
||||
RowsAffected uint64
|
||||
}
|
||||
|
||||
// Rows holds a result set encoded in a message body.
|
||||
type Rows struct {
|
||||
Columns []string
|
||||
message *Message
|
||||
}
|
||||
|
||||
// Next returns the next row in the result set.
|
||||
func (r *Rows) Next(dest []driver.Value) error {
|
||||
types := make([]uint8, len(r.Columns))
|
||||
|
||||
// Each column needs a 4 byte slot to store the column type. The row
|
||||
// header must be padded to reach word boundary.
|
||||
headerBits := len(types) * 4
|
||||
padBits := 0
|
||||
if trailingBits := (headerBits % messageWordBits); trailingBits != 0 {
|
||||
padBits = (messageWordBits - trailingBits)
|
||||
}
|
||||
|
||||
headerSize := (headerBits + padBits) / messageWordBits * messageWordSize
|
||||
|
||||
for i := 0; i < headerSize; i++ {
|
||||
slot := r.message.getUint8()
|
||||
|
||||
if slot == 0xee {
|
||||
// More rows are available.
|
||||
return ErrRowsPart
|
||||
}
|
||||
|
||||
if slot == 0xff {
|
||||
// Rows EOF marker
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
index := i * 2
|
||||
|
||||
if index >= len(types) {
|
||||
continue // This is padding.
|
||||
}
|
||||
|
||||
types[index] = slot & 0x0f
|
||||
|
||||
index++
|
||||
|
||||
if index >= len(types) {
|
||||
continue // This is padding byte.
|
||||
}
|
||||
|
||||
types[index] = slot >> 4
|
||||
}
|
||||
|
||||
for i := range types {
|
||||
switch types[i] {
|
||||
case Integer:
|
||||
dest[i] = r.message.getInt64()
|
||||
case Float:
|
||||
dest[i] = r.message.getFloat64()
|
||||
case Blob:
|
||||
dest[i] = r.message.getBlob()
|
||||
case Text:
|
||||
dest[i] = r.message.getString()
|
||||
case Null:
|
||||
r.message.getUint64()
|
||||
dest[i] = nil
|
||||
case UnixTime:
|
||||
timestamp := time.Unix(r.message.getInt64(), 0)
|
||||
dest[i] = timestamp
|
||||
case ISO8601:
|
||||
value := r.message.getString()
|
||||
if value == "" {
|
||||
dest[i] = time.Time{}
|
||||
break
|
||||
}
|
||||
var t time.Time
|
||||
var timeVal time.Time
|
||||
var err error
|
||||
value = strings.TrimSuffix(value, "Z")
|
||||
for _, format := range iso8601Formats {
|
||||
if timeVal, err = time.ParseInLocation(format, value, time.UTC); err == nil {
|
||||
t = timeVal
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t = t.In(time.Local)
|
||||
dest[i] = t
|
||||
case Boolean:
|
||||
dest[i] = r.message.getInt64() != 0
|
||||
default:
|
||||
panic("unknown data type")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the result set and reset the underlying message.
|
||||
func (r *Rows) Close() error {
|
||||
// If we didn't go through all rows, let's look at the last byte.
|
||||
var err error
|
||||
if !r.message.hasBeenConsumed() {
|
||||
slot := r.message.lastByte()
|
||||
if slot == 0xee {
|
||||
// More rows are available.
|
||||
err = ErrRowsPart
|
||||
} else if slot == 0xff {
|
||||
// Rows EOF marker
|
||||
err = io.EOF
|
||||
} else {
|
||||
err = fmt.Errorf("unexpected end of message")
|
||||
}
|
||||
}
|
||||
r.message.Reset()
|
||||
return err
|
||||
}
|
||||
|
||||
// Files holds a set of files encoded in a message body.
|
||||
type Files struct {
|
||||
n uint64
|
||||
message *Message
|
||||
}
|
||||
|
||||
func (f *Files) Next() (string, []byte) {
|
||||
if f.n == 0 {
|
||||
return "", nil
|
||||
}
|
||||
f.n--
|
||||
name := f.message.getString()
|
||||
length := f.message.getUint64()
|
||||
data := make([]byte, length)
|
||||
for i := 0; i < int(length); i++ {
|
||||
data[i] = f.message.getUint8()
|
||||
}
|
||||
return name, data
|
||||
}
|
||||
|
||||
func (f *Files) Close() {
|
||||
f.message.Reset()
|
||||
}
|
||||
|
||||
const (
|
||||
messageWordSize = 8
|
||||
messageWordBits = messageWordSize * 8
|
||||
messageHeaderSize = messageWordSize
|
||||
messageMaxConsecutiveEmptyReads = 100
|
||||
)
|
||||
|
||||
var iso8601Formats = []string{
|
||||
// By default, store timestamps with whatever timezone they come with.
|
||||
// When parsed, they will be returned with the same timezone.
|
||||
"2006-01-02 15:04:05.999999999-07:00",
|
||||
"2006-01-02T15:04:05.999999999-07:00",
|
||||
"2006-01-02 15:04:05.999999999",
|
||||
"2006-01-02T15:04:05.999999999",
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02 15:04",
|
||||
"2006-01-02T15:04",
|
||||
"2006-01-02",
|
||||
}
|
||||
340
vendor/github.com/canonical/go-dqlite/internal/protocol/protocol.go
generated
vendored
Normal file
340
vendor/github.com/canonical/go-dqlite/internal/protocol/protocol.go
generated
vendored
Normal file
@@ -0,0 +1,340 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Protocol sends and receive the dqlite message on the wire.
|
||||
type Protocol struct {
|
||||
version uint64 // Protocol version
|
||||
conn net.Conn // Underlying network connection.
|
||||
contextTimeout time.Duration // Default context timeout.
|
||||
closeCh chan struct{} // Stops the heartbeat when the connection gets closed
|
||||
mu sync.Mutex // Serialize requests
|
||||
netErr error // A network error occurred
|
||||
}
|
||||
|
||||
func NewProtocol(version uint64, conn net.Conn) *Protocol {
|
||||
protocol := &Protocol{
|
||||
version: version,
|
||||
conn: conn,
|
||||
closeCh: make(chan struct{}),
|
||||
contextTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
return protocol
|
||||
}
|
||||
|
||||
// SetContextTimeout sets the default context timeout when no deadline is
|
||||
// provided.
|
||||
func (p *Protocol) SetContextTimeout(timeout time.Duration) {
|
||||
p.contextTimeout = timeout
|
||||
}
|
||||
|
||||
// Call invokes a dqlite RPC, sending a request message and receiving a
|
||||
// response message.
|
||||
func (p *Protocol) Call(ctx context.Context, request, response *Message) (err error) {
|
||||
// We need to take a lock since the dqlite server currently does not
|
||||
// support concurrent requests.
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.netErr != nil {
|
||||
return p.netErr
|
||||
}
|
||||
|
||||
// Honor the ctx deadline, if present, or use a default.
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
deadline = time.Now().Add(p.contextTimeout)
|
||||
}
|
||||
|
||||
p.conn.SetDeadline(deadline)
|
||||
|
||||
if err = p.send(request); err != nil {
|
||||
err = errors.Wrap(err, "failed to send request")
|
||||
goto err
|
||||
}
|
||||
|
||||
if err = p.recv(response); err != nil {
|
||||
err = errors.Wrap(err, "failed to receive response")
|
||||
goto err
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
err:
|
||||
switch errors.Cause(err).(type) {
|
||||
case *net.OpError:
|
||||
p.netErr = err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// More is used when a request maps to multiple responses.
|
||||
func (p *Protocol) More(ctx context.Context, response *Message) error {
|
||||
return p.recv(response)
|
||||
}
|
||||
|
||||
// Interrupt sends an interrupt request and awaits for the server's empty
|
||||
// response.
|
||||
func (p *Protocol) Interrupt(ctx context.Context, request *Message, response *Message) error {
|
||||
// We need to take a lock since the dqlite server currently does not
|
||||
// support concurrent requests.
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
// Honor the ctx deadline, if present, or use a default.
|
||||
deadline, ok := ctx.Deadline()
|
||||
if !ok {
|
||||
deadline = time.Now().Add(2 * time.Second)
|
||||
}
|
||||
p.conn.SetDeadline(deadline)
|
||||
|
||||
defer request.Reset()
|
||||
|
||||
EncodeInterrupt(request, 0)
|
||||
|
||||
if err := p.send(request); err != nil {
|
||||
return errors.Wrap(err, "failed to send interrupt request")
|
||||
}
|
||||
|
||||
for {
|
||||
if err := p.recv(response); err != nil {
|
||||
response.Reset()
|
||||
return errors.Wrap(err, "failed to receive response")
|
||||
}
|
||||
|
||||
mtype, _ := response.getHeader()
|
||||
response.Reset()
|
||||
|
||||
if mtype == ResponseEmpty {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close the client connection.
|
||||
func (p *Protocol) Close() error {
|
||||
close(p.closeCh)
|
||||
return p.conn.Close()
|
||||
}
|
||||
|
||||
func (p *Protocol) send(req *Message) error {
|
||||
if err := p.sendHeader(req); err != nil {
|
||||
return errors.Wrap(err, "failed to send header")
|
||||
}
|
||||
|
||||
if err := p.sendBody(req); err != nil {
|
||||
return errors.Wrap(err, "failed to send body")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Protocol) sendHeader(req *Message) error {
|
||||
n, err := p.conn.Write(req.header[:])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to send header")
|
||||
}
|
||||
|
||||
if n != messageHeaderSize {
|
||||
return errors.Wrap(io.ErrShortWrite, "failed to send header")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Protocol) sendBody(req *Message) error {
|
||||
buf := req.body1.Bytes[:req.body1.Offset]
|
||||
n, err := p.conn.Write(buf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to send static body")
|
||||
}
|
||||
|
||||
if n != len(buf) {
|
||||
return errors.Wrap(io.ErrShortWrite, "failed to write body")
|
||||
}
|
||||
|
||||
if req.body2.Bytes == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf = req.body2.Bytes[:req.body2.Offset]
|
||||
n, err = p.conn.Write(buf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to send dynamic body")
|
||||
}
|
||||
|
||||
if n != len(buf) {
|
||||
return errors.Wrap(io.ErrShortWrite, "failed to write body")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Protocol) recv(res *Message) error {
|
||||
if err := p.recvHeader(res); err != nil {
|
||||
return errors.Wrap(err, "failed to receive header")
|
||||
}
|
||||
|
||||
if err := p.recvBody(res); err != nil {
|
||||
return errors.Wrap(err, "failed to receive body")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Protocol) recvHeader(res *Message) error {
|
||||
if err := p.recvPeek(res.header); err != nil {
|
||||
return errors.Wrap(err, "failed to receive header")
|
||||
}
|
||||
|
||||
res.words = binary.LittleEndian.Uint32(res.header[0:])
|
||||
res.mtype = res.header[4]
|
||||
res.flags = res.header[5]
|
||||
res.extra = binary.LittleEndian.Uint16(res.header[6:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Protocol) recvBody(res *Message) error {
|
||||
n := int(res.words) * messageWordSize
|
||||
n1 := n
|
||||
n2 := 0
|
||||
|
||||
if n1 > len(res.body1.Bytes) {
|
||||
// We need to allocate the dynamic buffer.
|
||||
n1 = len(res.body1.Bytes)
|
||||
n2 = n - n1
|
||||
}
|
||||
|
||||
buf := res.body1.Bytes[:n1]
|
||||
|
||||
if err := p.recvPeek(buf); err != nil {
|
||||
return errors.Wrap(err, "failed to read body")
|
||||
}
|
||||
|
||||
if n2 > 0 {
|
||||
res.body2.Bytes = make([]byte, n2)
|
||||
res.body2.Offset = 0
|
||||
buf = res.body2.Bytes
|
||||
if err := p.recvPeek(buf); err != nil {
|
||||
return errors.Wrap(err, "failed to read body")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read until buf is full.
|
||||
func (p *Protocol) recvPeek(buf []byte) error {
|
||||
for offset := 0; offset < len(buf); {
|
||||
n, err := p.recvFill(buf[offset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset += n
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try to fill buf, but perform at most one read.
|
||||
func (p *Protocol) recvFill(buf []byte) (int, error) {
|
||||
// Read new data: try a limited number of times.
|
||||
//
|
||||
// This technique is copied from bufio.Reader.
|
||||
for i := messageMaxConsecutiveEmptyReads; i > 0; i-- {
|
||||
n, err := p.conn.Read(buf)
|
||||
if n < 0 {
|
||||
panic(errNegativeRead)
|
||||
}
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if n > 0 {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
return -1, io.ErrNoProgress
|
||||
}
|
||||
|
||||
/*
|
||||
func (p *Protocol) heartbeat() {
|
||||
request := Message{}
|
||||
request.Init(16)
|
||||
response := Message{}
|
||||
response.Init(512)
|
||||
|
||||
for {
|
||||
delay := c.heartbeatTimeout / 3
|
||||
|
||||
//c.logger.Debug("sending heartbeat", zap.Duration("delay", delay))
|
||||
time.Sleep(delay)
|
||||
|
||||
// Check if we've been closed.
|
||||
select {
|
||||
case <-c.closeCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
|
||||
EncodeHeartbeat(&request, uint64(time.Now().Unix()))
|
||||
|
||||
err := c.Call(ctx, &request, &response)
|
||||
|
||||
// We bail out upon failures.
|
||||
//
|
||||
// TODO: make the client survive temporary disconnections.
|
||||
if err != nil {
|
||||
cancel()
|
||||
//c.logger.Error("heartbeat failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
//addresses, err := DecodeNodes(&response)
|
||||
_, err = DecodeNodes(&response)
|
||||
if err != nil {
|
||||
cancel()
|
||||
//c.logger.Error("invalid heartbeat response", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// if err := c.store.Set(ctx, addresses); err != nil {
|
||||
// cancel()
|
||||
// c.logger.Error("failed to update servers", zap.Error(err))
|
||||
// return
|
||||
// }
|
||||
|
||||
cancel()
|
||||
|
||||
request.Reset()
|
||||
response.Reset()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// DecodeNodeCompat handles also pre-1.0 legacy server messages.
|
||||
func DecodeNodeCompat(protocol *Protocol, response *Message) (uint64, string, error) {
|
||||
if protocol.version == VersionLegacy {
|
||||
address, err := DecodeNodeLegacy(response)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
return 0, address, nil
|
||||
|
||||
}
|
||||
return DecodeNode(response)
|
||||
}
|
||||
131
vendor/github.com/canonical/go-dqlite/internal/protocol/request.go
generated
vendored
Normal file
131
vendor/github.com/canonical/go-dqlite/internal/protocol/request.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
package protocol
|
||||
|
||||
// DO NOT EDIT
|
||||
//
|
||||
// This file was generated by ./schema.sh
|
||||
|
||||
|
||||
// EncodeLeader encodes a Leader request.
|
||||
func EncodeLeader(request *Message) {
|
||||
request.putUint64(0)
|
||||
|
||||
request.putHeader(RequestLeader)
|
||||
}
|
||||
|
||||
// EncodeClient encodes a Client request.
|
||||
func EncodeClient(request *Message, id uint64) {
|
||||
request.putUint64(id)
|
||||
|
||||
request.putHeader(RequestClient)
|
||||
}
|
||||
|
||||
// EncodeHeartbeat encodes a Heartbeat request.
|
||||
func EncodeHeartbeat(request *Message, timestamp uint64) {
|
||||
request.putUint64(timestamp)
|
||||
|
||||
request.putHeader(RequestHeartbeat)
|
||||
}
|
||||
|
||||
// EncodeOpen encodes a Open request.
|
||||
func EncodeOpen(request *Message, name string, flags uint64, vfs string) {
|
||||
request.putString(name)
|
||||
request.putUint64(flags)
|
||||
request.putString(vfs)
|
||||
|
||||
request.putHeader(RequestOpen)
|
||||
}
|
||||
|
||||
// EncodePrepare encodes a Prepare request.
|
||||
func EncodePrepare(request *Message, db uint64, sql string) {
|
||||
request.putUint64(db)
|
||||
request.putString(sql)
|
||||
|
||||
request.putHeader(RequestPrepare)
|
||||
}
|
||||
|
||||
// EncodeExec encodes a Exec request.
|
||||
func EncodeExec(request *Message, db uint32, stmt uint32, values NamedValues) {
|
||||
request.putUint32(db)
|
||||
request.putUint32(stmt)
|
||||
request.putNamedValues(values)
|
||||
|
||||
request.putHeader(RequestExec)
|
||||
}
|
||||
|
||||
// EncodeQuery encodes a Query request.
|
||||
func EncodeQuery(request *Message, db uint32, stmt uint32, values NamedValues) {
|
||||
request.putUint32(db)
|
||||
request.putUint32(stmt)
|
||||
request.putNamedValues(values)
|
||||
|
||||
request.putHeader(RequestQuery)
|
||||
}
|
||||
|
||||
// EncodeFinalize encodes a Finalize request.
|
||||
func EncodeFinalize(request *Message, db uint32, stmt uint32) {
|
||||
request.putUint32(db)
|
||||
request.putUint32(stmt)
|
||||
|
||||
request.putHeader(RequestFinalize)
|
||||
}
|
||||
|
||||
// EncodeExecSQL encodes a ExecSQL request.
|
||||
func EncodeExecSQL(request *Message, db uint64, sql string, values NamedValues) {
|
||||
request.putUint64(db)
|
||||
request.putString(sql)
|
||||
request.putNamedValues(values)
|
||||
|
||||
request.putHeader(RequestExecSQL)
|
||||
}
|
||||
|
||||
// EncodeQuerySQL encodes a QuerySQL request.
|
||||
func EncodeQuerySQL(request *Message, db uint64, sql string, values NamedValues) {
|
||||
request.putUint64(db)
|
||||
request.putString(sql)
|
||||
request.putNamedValues(values)
|
||||
|
||||
request.putHeader(RequestQuerySQL)
|
||||
}
|
||||
|
||||
// EncodeInterrupt encodes a Interrupt request.
|
||||
func EncodeInterrupt(request *Message, db uint64) {
|
||||
request.putUint64(db)
|
||||
|
||||
request.putHeader(RequestInterrupt)
|
||||
}
|
||||
|
||||
// EncodeJoin encodes a Join request.
|
||||
func EncodeJoin(request *Message, id uint64, address string) {
|
||||
request.putUint64(id)
|
||||
request.putString(address)
|
||||
|
||||
request.putHeader(RequestJoin)
|
||||
}
|
||||
|
||||
// EncodePromote encodes a Promote request.
|
||||
func EncodePromote(request *Message, id uint64) {
|
||||
request.putUint64(id)
|
||||
|
||||
request.putHeader(RequestPromote)
|
||||
}
|
||||
|
||||
// EncodeRemove encodes a Remove request.
|
||||
func EncodeRemove(request *Message, id uint64) {
|
||||
request.putUint64(id)
|
||||
|
||||
request.putHeader(RequestRemove)
|
||||
}
|
||||
|
||||
// EncodeDump encodes a Dump request.
|
||||
func EncodeDump(request *Message, name string) {
|
||||
request.putString(name)
|
||||
|
||||
request.putHeader(RequestDump)
|
||||
}
|
||||
|
||||
// EncodeCluster encodes a Cluster request.
|
||||
func EncodeCluster(request *Message) {
|
||||
request.putUint64(0)
|
||||
|
||||
request.putHeader(RequestCluster)
|
||||
}
|
||||
254
vendor/github.com/canonical/go-dqlite/internal/protocol/response.go
generated
vendored
Normal file
254
vendor/github.com/canonical/go-dqlite/internal/protocol/response.go
generated
vendored
Normal file
@@ -0,0 +1,254 @@
|
||||
package protocol
|
||||
|
||||
// DO NOT EDIT
|
||||
//
|
||||
// This file was generated by ./schema.sh
|
||||
|
||||
import "fmt"
|
||||
|
||||
// DecodeFailure decodes a Failure response.
|
||||
func DecodeFailure(response *Message) (code uint64, message string, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseFailure {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
code = response.getUint64()
|
||||
message = response.getString()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeWelcome decodes a Welcome response.
|
||||
func DecodeWelcome(response *Message) (heartbeatTimeout uint64, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseWelcome {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
heartbeatTimeout = response.getUint64()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeNodeLegacy decodes a NodeLegacy response.
|
||||
func DecodeNodeLegacy(response *Message) (address string, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseNodeLegacy {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
address = response.getString()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeNode decodes a Node response.
|
||||
func DecodeNode(response *Message) (id uint64, address string, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseNode {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
id = response.getUint64()
|
||||
address = response.getString()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeNodes decodes a Nodes response.
|
||||
func DecodeNodes(response *Message) (servers Nodes, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseNodes {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
servers = response.getNodes()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeDb decodes a Db response.
|
||||
func DecodeDb(response *Message) (id uint32, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseDb {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
id = response.getUint32()
|
||||
response.getUint32()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeStmt decodes a Stmt response.
|
||||
func DecodeStmt(response *Message) (db uint32, id uint32, params uint64, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseStmt {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
db = response.getUint32()
|
||||
id = response.getUint32()
|
||||
params = response.getUint64()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeEmpty decodes a Empty response.
|
||||
func DecodeEmpty(response *Message) (err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseEmpty {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
response.getUint64()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeResult decodes a Result response.
|
||||
func DecodeResult(response *Message) (result Result, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseResult {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
result = response.getResult()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeRows decodes a Rows response.
|
||||
func DecodeRows(response *Message) (rows Rows, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseRows {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
rows = response.getRows()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeFiles decodes a Files response.
|
||||
func DecodeFiles(response *Message) (files Files, err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != ResponseFiles {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
files = response.getFiles()
|
||||
|
||||
return
|
||||
}
|
||||
33
vendor/github.com/canonical/go-dqlite/internal/protocol/schema.go
generated
vendored
Normal file
33
vendor/github.com/canonical/go-dqlite/internal/protocol/schema.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package protocol
|
||||
|
||||
//go:generate ./schema.sh --request init
|
||||
|
||||
//go:generate ./schema.sh --request Leader unused:uint64
|
||||
//go:generate ./schema.sh --request Client id:uint64
|
||||
//go:generate ./schema.sh --request Heartbeat timestamp:uint64
|
||||
//go:generate ./schema.sh --request Open name:string flags:uint64 vfs:string
|
||||
//go:generate ./schema.sh --request Prepare db:uint64 sql:string
|
||||
//go:generate ./schema.sh --request Exec db:uint32 stmt:uint32 values:NamedValues
|
||||
//go:generate ./schema.sh --request Query db:uint32 stmt:uint32 values:NamedValues
|
||||
//go:generate ./schema.sh --request Finalize db:uint32 stmt:uint32
|
||||
//go:generate ./schema.sh --request ExecSQL db:uint64 sql:string values:NamedValues
|
||||
//go:generate ./schema.sh --request QuerySQL db:uint64 sql:string values:NamedValues
|
||||
//go:generate ./schema.sh --request Interrupt db:uint64
|
||||
//go:generate ./schema.sh --request Join id:uint64 address:string
|
||||
//go:generate ./schema.sh --request Promote id:uint64
|
||||
//go:generate ./schema.sh --request Remove id:uint64
|
||||
//go:generate ./schema.sh --request Dump name:string
|
||||
//go:generate ./schema.sh --request Cluster unused:uint64
|
||||
|
||||
//go:generate ./schema.sh --response init
|
||||
//go:generate ./schema.sh --response Failure code:uint64 message:string
|
||||
//go:generate ./schema.sh --response Welcome heartbeatTimeout:uint64
|
||||
//go:generate ./schema.sh --response NodeLegacy address:string
|
||||
//go:generate ./schema.sh --response Node id:uint64 address:string
|
||||
//go:generate ./schema.sh --response Nodes servers:Nodes
|
||||
//go:generate ./schema.sh --response Db id:uint32 unused:uint32
|
||||
//go:generate ./schema.sh --response Stmt db:uint32 id:uint32 params:uint64
|
||||
//go:generate ./schema.sh --response Empty unused:uint64
|
||||
//go:generate ./schema.sh --response Result result:Result
|
||||
//go:generate ./schema.sh --response Rows rows:Rows
|
||||
//go:generate ./schema.sh --response Files files:Files
|
||||
144
vendor/github.com/canonical/go-dqlite/internal/protocol/schema.sh
generated
vendored
Normal file
144
vendor/github.com/canonical/go-dqlite/internal/protocol/schema.sh
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
|
||||
request_init() {
|
||||
cat > request.go <<EOF
|
||||
package protocol
|
||||
|
||||
// DO NOT EDIT
|
||||
//
|
||||
// This file was generated by ./schema.sh
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
response_init() {
|
||||
cat > response.go <<EOF
|
||||
package protocol
|
||||
|
||||
// DO NOT EDIT
|
||||
//
|
||||
// This file was generated by ./schema.sh
|
||||
|
||||
import "fmt"
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
entity=$1
|
||||
shift
|
||||
|
||||
cmd=$1
|
||||
shift
|
||||
|
||||
if [ "$entity" = "--request" ]; then
|
||||
if [ "$cmd" = "init" ]; then
|
||||
request_init
|
||||
exit
|
||||
fi
|
||||
|
||||
args=""
|
||||
|
||||
for i in "${@}"
|
||||
do
|
||||
name=$(echo "$i" | cut -f 1 -d :)
|
||||
type=$(echo "$i" | cut -f 2 -d :)
|
||||
|
||||
if [ "$name" = "unused" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
args=$(echo "${args}, ${name} ${type}")
|
||||
done
|
||||
|
||||
cat >> request.go <<EOF
|
||||
|
||||
// Encode${cmd} encodes a $cmd request.
|
||||
func Encode${cmd}(request *Message${args}) {
|
||||
EOF
|
||||
|
||||
for i in "${@}"
|
||||
do
|
||||
name=$(echo "$i" | cut -f 1 -d :)
|
||||
type=$(echo "$i" | cut -f 2 -d :)
|
||||
|
||||
if [ "$name" = "unused" ]; then
|
||||
name=$(echo "0")
|
||||
fi
|
||||
|
||||
cat >> request.go <<EOF
|
||||
request.put${type^}(${name})
|
||||
EOF
|
||||
done
|
||||
|
||||
cat >> request.go <<EOF
|
||||
|
||||
request.putHeader(Request${cmd})
|
||||
}
|
||||
EOF
|
||||
|
||||
fi
|
||||
|
||||
if [ "$entity" = "--response" ]; then
|
||||
if [ "$cmd" = "init" ]; then
|
||||
response_init
|
||||
exit
|
||||
fi
|
||||
|
||||
returns=""
|
||||
|
||||
for i in "${@}"
|
||||
do
|
||||
name=$(echo "$i" | cut -f 1 -d :)
|
||||
type=$(echo "$i" | cut -f 2 -d :)
|
||||
|
||||
if [ "$name" = "unused" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
returns=$(echo "${returns}${name} ${type}, ")
|
||||
done
|
||||
|
||||
cat >> response.go <<EOF
|
||||
|
||||
// Decode${cmd} decodes a $cmd response.
|
||||
func Decode${cmd}(response *Message) (${returns}err error) {
|
||||
mtype, _ := response.getHeader()
|
||||
|
||||
if mtype == ResponseFailure {
|
||||
e := ErrRequest{}
|
||||
e.Code = response.getUint64()
|
||||
e.Description = response.getString()
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
if mtype != Response${cmd} {
|
||||
err = fmt.Errorf("unexpected response type %d", mtype)
|
||||
return
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
for i in "${@}"
|
||||
do
|
||||
name=$(echo "$i" | cut -f 1 -d :)
|
||||
type=$(echo "$i" | cut -f 2 -d :)
|
||||
|
||||
assign=$(echo "${name} = ")
|
||||
|
||||
if [ "$name" = "unused" ]; then
|
||||
assign=$(echo "")
|
||||
fi
|
||||
|
||||
cat >> response.go <<EOF
|
||||
${assign}response.get${type^}()
|
||||
EOF
|
||||
done
|
||||
|
||||
cat >> response.go <<EOF
|
||||
|
||||
return
|
||||
}
|
||||
EOF
|
||||
|
||||
fi
|
||||
49
vendor/github.com/canonical/go-dqlite/internal/protocol/store.go
generated
vendored
Normal file
49
vendor/github.com/canonical/go-dqlite/internal/protocol/store.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// NodeInfo holds information about a single server.
|
||||
type NodeInfo struct {
|
||||
ID uint64
|
||||
Address string
|
||||
}
|
||||
|
||||
// NodeStore is used by a dqlite client to get an initial list of candidate
|
||||
// dqlite servers that it can dial in order to find a leader server to connect
|
||||
// to.
|
||||
//
|
||||
// Once connected, the client periodically updates the server addresses in the
|
||||
// store by querying the leader about changes in the cluster (such as servers
|
||||
// being added or removed).
|
||||
type NodeStore interface {
|
||||
// Get return the list of known servers.
|
||||
Get(context.Context) ([]NodeInfo, error)
|
||||
|
||||
// Set updates the list of known cluster servers.
|
||||
Set(context.Context, []NodeInfo) error
|
||||
}
|
||||
|
||||
// InmemNodeStore keeps the list of servers in memory.
|
||||
type InmemNodeStore struct {
|
||||
servers []NodeInfo
|
||||
}
|
||||
|
||||
// NewInmemNodeStore creates NodeStore which stores its data in-memory.
|
||||
func NewInmemNodeStore() *InmemNodeStore {
|
||||
return &InmemNodeStore{
|
||||
servers: make([]NodeInfo, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current servers.
|
||||
func (i *InmemNodeStore) Get(ctx context.Context) ([]NodeInfo, error) {
|
||||
return i.servers, nil
|
||||
}
|
||||
|
||||
// Set the servers.
|
||||
func (i *InmemNodeStore) Set(ctx context.Context, servers []NodeInfo) error {
|
||||
i.servers = servers
|
||||
return nil
|
||||
}
|
||||
126
vendor/github.com/canonical/go-dqlite/node.go
generated
vendored
Normal file
126
vendor/github.com/canonical/go-dqlite/node.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
package dqlite
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/canonical/go-dqlite/client"
|
||||
"github.com/canonical/go-dqlite/internal/bindings"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Node runs a dqlite node.
|
||||
type Node struct {
|
||||
log client.LogFunc // Logger
|
||||
server *bindings.Node // Low-level C implementation
|
||||
acceptCh chan error // Receives connection handling errors
|
||||
id uint64
|
||||
address string
|
||||
bindAddress string
|
||||
}
|
||||
|
||||
// NodeInfo is a convenience alias for client.NodeInfo.
|
||||
type NodeInfo = client.NodeInfo
|
||||
|
||||
// Option can be used to tweak node parameters.
|
||||
type Option func(*options)
|
||||
|
||||
// WithDialFunc sets a custom dial function for the server.
|
||||
func WithDialFunc(dial client.DialFunc) Option {
|
||||
return func(options *options) {
|
||||
options.DialFunc = dial
|
||||
}
|
||||
}
|
||||
|
||||
// WithBindAddress sets a custom bind address for the server.
|
||||
func WithBindAddress(address string) Option {
|
||||
return func(options *options) {
|
||||
options.BindAddress = address
|
||||
}
|
||||
}
|
||||
|
||||
// WithNetworkLatency sets the average one-way network latency.
|
||||
func WithNetworkLatency(latency time.Duration) Option {
|
||||
return func(options *options) {
|
||||
options.NetworkLatency = uint64(latency.Nanoseconds())
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new Node instance.
|
||||
func New(id uint64, address string, dir string, options ...Option) (*Node, error) {
|
||||
o := defaultOptions()
|
||||
|
||||
for _, option := range options {
|
||||
option(o)
|
||||
}
|
||||
|
||||
server, err := bindings.NewNode(id, address, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if o.DialFunc != nil {
|
||||
if err := server.SetDialFunc(o.DialFunc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if o.BindAddress != "" {
|
||||
if err := server.SetBindAddress(o.BindAddress); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if o.NetworkLatency != 0 {
|
||||
if err := server.SetNetworkLatency(o.NetworkLatency); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
s := &Node{
|
||||
server: server,
|
||||
acceptCh: make(chan error, 1),
|
||||
id: id,
|
||||
address: address,
|
||||
bindAddress: o.BindAddress,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// BindAddress returns the network address the node is listening to.
|
||||
func (s *Node) BindAddress() string {
|
||||
return s.server.GetBindAddress()
|
||||
}
|
||||
|
||||
// Start serving requests.
|
||||
func (s *Node) Start() error {
|
||||
return s.server.Start()
|
||||
}
|
||||
|
||||
// Recover a node by forcing a new cluster configuration.
|
||||
func (s *Node) Recover(cluster []NodeInfo) error {
|
||||
return s.server.Recover(cluster)
|
||||
}
|
||||
|
||||
// Hold configuration options for a dqlite server.
|
||||
type options struct {
|
||||
Log client.LogFunc
|
||||
DialFunc client.DialFunc
|
||||
BindAddress string
|
||||
NetworkLatency uint64
|
||||
}
|
||||
|
||||
// Close the server, releasing all resources it created.
|
||||
func (s *Node) Close() error {
|
||||
// Send a stop signal to the dqlite event loop.
|
||||
if err := s.server.Stop(); err != nil {
|
||||
return errors.Wrap(err, "server failed to stop")
|
||||
}
|
||||
|
||||
s.server.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a options object with sane defaults.
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
DialFunc: client.DefaultDialFunc,
|
||||
}
|
||||
}
|
||||
2
vendor/github.com/coreos/pkg/capnslog/init.go
generated
vendored
2
vendor/github.com/coreos/pkg/capnslog/init.go
generated
vendored
@@ -32,7 +32,7 @@ import (
|
||||
func init() {
|
||||
initHijack()
|
||||
|
||||
// Go `log` pacakge uses os.Stderr.
|
||||
// Go `log` package uses os.Stderr.
|
||||
SetFormatter(NewDefaultFormatter(os.Stderr))
|
||||
SetGlobalLogLevel(INFO)
|
||||
}
|
||||
|
||||
1
vendor/github.com/flosch/pongo2/.gitattributes
generated
vendored
Normal file
1
vendor/github.com/flosch/pongo2/.gitattributes
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text eol=lf
|
||||
42
vendor/github.com/flosch/pongo2/.gitignore
generated
vendored
Normal file
42
vendor/github.com/flosch/pongo2/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
|
||||
.project
|
||||
EBNF.txt
|
||||
test1.tpl
|
||||
pongo2_internal_test.go
|
||||
tpl-error.out
|
||||
/count.out
|
||||
/cover.out
|
||||
*.swp
|
||||
*.iml
|
||||
/cpu.out
|
||||
/mem.out
|
||||
/pongo2.test
|
||||
*.error
|
||||
/profile
|
||||
/coverage.out
|
||||
/pongo2_internal_test.ignore
|
||||
go.sum
|
||||
8
vendor/github.com/flosch/pongo2/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/flosch/pongo2/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
language: go
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
go:
|
||||
- 1.12
|
||||
script:
|
||||
- go test -v
|
||||
11
vendor/github.com/flosch/pongo2/AUTHORS
generated
vendored
Normal file
11
vendor/github.com/flosch/pongo2/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
Main author and maintainer of pongo2:
|
||||
|
||||
* Florian Schlachter <flori@n-schlachter.de>
|
||||
|
||||
Contributors (in no specific order):
|
||||
|
||||
* @romanoaugusto88
|
||||
* @vitalbh
|
||||
* @blaubaer
|
||||
|
||||
Feel free to add yourself to the list or to modify your entry if you did a contribution.
|
||||
20
vendor/github.com/flosch/pongo2/LICENSE
generated
vendored
Normal file
20
vendor/github.com/flosch/pongo2/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2014 Florian Schlachter
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
273
vendor/github.com/flosch/pongo2/README.md
generated
vendored
Normal file
273
vendor/github.com/flosch/pongo2/README.md
generated
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
# [pongo](https://en.wikipedia.org/wiki/Pongo_%28genus%29)2
|
||||
|
||||
[](https://gitter.im/flosch/pongo2)
|
||||
[](https://godoc.org/github.com/flosch/pongo2)
|
||||
[](https://travis-ci.org/flosch/pongo2)
|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
pongo2 is the successor of [pongo](https://github.com/flosch/pongo), a Django-syntax like templating-language.
|
||||
|
||||
Install/update using `go get` (no dependencies required by pongo2):
|
||||
```
|
||||
go get -u github.com/flosch/pongo2
|
||||
```
|
||||
|
||||
Please use the [issue tracker](https://github.com/flosch/pongo2/issues) if you're encountering any problems with pongo2 or if you need help with implementing tags or filters ([create a ticket!](https://github.com/flosch/pongo2/issues/new)).
|
||||
|
||||
## First impression of a template
|
||||
|
||||
```HTML+Django
|
||||
<html><head><title>Our admins and users</title></head>
|
||||
{# This is a short example to give you a quick overview of pongo2's syntax. #}
|
||||
|
||||
{% macro user_details(user, is_admin=false) %}
|
||||
<div class="user_item">
|
||||
<!-- Let's indicate a user's good karma -->
|
||||
<h2 {% if (user.karma >= 40) || (user.karma > calc_avg_karma(userlist)+5) %}
|
||||
class="karma-good"{% endif %}>
|
||||
|
||||
<!-- This will call user.String() automatically if available: -->
|
||||
{{ user }}
|
||||
</h2>
|
||||
|
||||
<!-- Will print a human-readable time duration like "3 weeks ago" -->
|
||||
<p>This user registered {{ user.register_date|naturaltime }}.</p>
|
||||
|
||||
<!-- Let's allow the users to write down their biography using markdown;
|
||||
we will only show the first 15 words as a preview -->
|
||||
<p>The user's biography:</p>
|
||||
<p>{{ user.biography|markdown|truncatewords_html:15 }}
|
||||
<a href="/user/{{ user.id }}/">read more</a></p>
|
||||
|
||||
{% if is_admin %}<p>This user is an admin!</p>{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<body>
|
||||
<!-- Make use of the macro defined above to avoid repetitive HTML code
|
||||
since we want to use the same code for admins AND members -->
|
||||
|
||||
<h1>Our admins</h1>
|
||||
{% for admin in adminlist %}
|
||||
{{ user_details(admin, true) }}
|
||||
{% endfor %}
|
||||
|
||||
<h1>Our members</h1>
|
||||
{% for user in userlist %}
|
||||
{{ user_details(user) }}
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Development status
|
||||
|
||||
**Latest stable release**: v3.0 (`go get -u gopkg.in/flosch/pongo2.v3` / [`v3`](https://github.com/flosch/pongo2/tree/v3)-branch)
|
||||
|
||||
**Current development**: v4 (`master`-branch)
|
||||
|
||||
*Note*: With the release of pongo v4 the branch v2 will be deprecated.
|
||||
|
||||
**Deprecated versions** (not supported anymore): v1
|
||||
|
||||
| Topic | Status |
|
||||
| ------------------------------------ | -------------------------------------------------------------------------------------- |
|
||||
| Django version compatibility: | [1.7](https://docs.djangoproject.com/en/1.7/ref/templates/builtins/) |
|
||||
| *Missing* (planned) **filters**: | none ([hints](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3)) |
|
||||
| *Missing* (planned) **tags**: | none ([hints](https://github.com/flosch/pongo2/blob/master/tags.go#L3)) |
|
||||
|
||||
Please also have a look on the [caveats](https://github.com/flosch/pongo2#caveats) and on the [official add-ons](https://github.com/flosch/pongo2#official).
|
||||
|
||||
## Features (and new in pongo2)
|
||||
|
||||
* Entirely rewritten from the ground-up.
|
||||
* [Advanced C-like expressions](https://github.com/flosch/pongo2/blob/master/template_tests/expressions.tpl).
|
||||
* [Complex function calls within expressions](https://github.com/flosch/pongo2/blob/master/template_tests/function_calls_wrapper.tpl).
|
||||
* [Easy API to create new filters and tags](http://godoc.org/github.com/flosch/pongo2#RegisterFilter) ([including parsing arguments](http://godoc.org/github.com/flosch/pongo2#Parser))
|
||||
* Additional features:
|
||||
* Macros including importing macros from other files (see [template_tests/macro.tpl](https://github.com/flosch/pongo2/blob/master/template_tests/macro.tpl))
|
||||
* [Template sandboxing](https://godoc.org/github.com/flosch/pongo2#TemplateSet) ([directory patterns](http://golang.org/pkg/path/filepath/#Match), banned tags/filters)
|
||||
|
||||
## Recent API changes within pongo2
|
||||
|
||||
If you're using the `master`-branch of pongo2, you might be interested in this section. Since pongo2 is still in development (even though there is a first stable release!), there could be (backwards-incompatible) API changes over time. To keep track of these and therefore make it painless for you to adapt your codebase, I'll list them here.
|
||||
|
||||
* Function signature for tag execution changed: not taking a `bytes.Buffer` anymore; instead `Execute()`-functions are now taking a `TemplateWriter` interface.
|
||||
* Function signature for tag and filter parsing/execution changed (`error` return type changed to `*Error`).
|
||||
* `INodeEvaluator` has been removed and got replaced by `IEvaluator`. You can change your existing tags/filters by simply replacing the interface.
|
||||
* Two new helper functions: [`RenderTemplateFile()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateFile) and [`RenderTemplateString()`](https://godoc.org/github.com/flosch/pongo2#RenderTemplateString).
|
||||
* `Template.ExecuteRW()` is now [`Template.ExecuteWriter()`](https://godoc.org/github.com/flosch/pongo2#Template.ExecuteWriter)
|
||||
* `Template.Execute*()` functions do now take a `pongo2.Context` directly (no pointer anymore).
|
||||
|
||||
## How you can help
|
||||
|
||||
* Write [filters](https://github.com/flosch/pongo2/blob/master/filters_builtin.go#L3) / [tags] by forking pongo2 and sending pull requests
|
||||
* Write/improve code tests (use the following command to see what tests are missing: `go test -v -cover -covermode=count -coverprofile=cover.out && go tool cover -html=cover.out` or have a look on [gocover.io/github.com/flosch/pongo2](http://gocover.io/github.com/flosch/pongo2))
|
||||
* Write/improve template tests (see the `template_tests/` directory)
|
||||
* Write middleware, libraries and websites using pongo2. :-)
|
||||
|
||||
# Documentation
|
||||
|
||||
For a documentation on how the templating language works you can [head over to the Django documentation](https://docs.djangoproject.com/en/dev/topics/templates/). pongo2 aims to be compatible with it.
|
||||
|
||||
You can access pongo2's API documentation on [godoc](https://godoc.org/github.com/flosch/pongo2).
|
||||
|
||||
## Caveats
|
||||
|
||||
### Filters
|
||||
|
||||
* **date** / **time**: The `date` and `time` filter are taking the Golang specific time- and date-format (not Django's one) currently. [Take a look on the format here](http://golang.org/pkg/time/#Time.Format).
|
||||
* **stringformat**: `stringformat` does **not** take Python's string format syntax as a parameter, instead it takes Go's. Essentially `{{ 3.14|stringformat:"pi is %.2f" }}` is `fmt.Sprintf("pi is %.2f", 3.14)`.
|
||||
* **escape** / **force_escape**: Unlike Django's behaviour, the `escape`-filter is applied immediately. Therefore there is no need for a `force_escape`-filter yet.
|
||||
|
||||
### Tags
|
||||
|
||||
* **for**: All the `forloop` fields (like `forloop.counter`) are written with a capital letter at the beginning. For example, the `counter` can be accessed by `forloop.Counter` and the parentloop by `forloop.Parentloop`.
|
||||
* **now**: takes Go's time format (see **date** and **time**-filter).
|
||||
|
||||
### Misc
|
||||
|
||||
* **not in-operator**: You can check whether a map/struct/string contains a key/field/substring by using the in-operator (or the negation of it):
|
||||
`{% if key in map %}Key is in map{% else %}Key not in map{% endif %}` or `{% if !(key in map) %}Key is NOT in map{% else %}Key is in map{% endif %}`.
|
||||
|
||||
# Add-ons, libraries and helpers
|
||||
|
||||
## Official
|
||||
|
||||
* [ponginae](https://github.com/flosch/ponginae) - A web-framework for Go (using pongo2).
|
||||
* [pongo2-tools](https://github.com/flosch/pongo2-tools) - Official tools and helpers for pongo2
|
||||
* [pongo2-addons](https://github.com/flosch/pongo2-addons) - Official additional filters/tags for pongo2 (for example a **markdown**-filter). They are in their own repository because they're relying on 3rd-party-libraries.
|
||||
|
||||
## 3rd-party
|
||||
|
||||
* [beego-pongo2](https://github.com/oal/beego-pongo2) - A tiny little helper for using Pongo2 with [Beego](https://github.com/astaxie/beego).
|
||||
* [beego-pongo2.v2](https://github.com/ipfans/beego-pongo2.v2) - Same as `beego-pongo2`, but for pongo2 v2.
|
||||
* [macaron-pongo2](https://github.com/macaron-contrib/pongo2) - pongo2 support for [Macaron](https://github.com/Unknwon/macaron), a modular web framework.
|
||||
* [ginpongo2](https://github.com/ngerakines/ginpongo2) - middleware for [gin](github.com/gin-gonic/gin) to use pongo2 templates
|
||||
* [Build'n support for Iris' template engine](https://github.com/kataras/iris)
|
||||
* [pongo2gin](https://gitlab.com/go-box/pongo2gin) - alternative renderer for [gin](github.com/gin-gonic/gin) to use pongo2 templates
|
||||
* [pongo2-trans](https://github.com/digitalcrab/pongo2trans) - `trans`-tag implementation for internationalization
|
||||
* [tpongo2](https://github.com/tango-contrib/tpongo2) - pongo2 support for [Tango](https://github.com/lunny/tango), a micro-kernel & pluggable web framework.
|
||||
* [p2cli](https://github.com/wrouesnel/p2cli) - command line templating utility based on pongo2
|
||||
|
||||
Please add your project to this list and send me a pull request when you've developed something nice for pongo2.
|
||||
|
||||
# API-usage examples
|
||||
|
||||
Please see the documentation for a full list of provided API methods.
|
||||
|
||||
## A tiny example (template string)
|
||||
|
||||
```Go
|
||||
// Compile the template first (i. e. creating the AST)
|
||||
tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Now you can render the template with the given
|
||||
// pongo2.Context how often you want to.
|
||||
out, err := tpl.Execute(pongo2.Context{"name": "florian"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(out) // Output: Hello Florian!
|
||||
```
|
||||
|
||||
## Example server-usage (template file)
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/flosch/pongo2"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Pre-compiling the templates at application startup using the
|
||||
// little Must()-helper function (Must() will panic if FromFile()
|
||||
// or FromString() will return with an error - that's it).
|
||||
// It's faster to pre-compile it anywhere at startup and only
|
||||
// execute the template later.
|
||||
var tplExample = pongo2.Must(pongo2.FromFile("example.html"))
|
||||
|
||||
func examplePage(w http.ResponseWriter, r *http.Request) {
|
||||
// Execute the template per HTTP request
|
||||
err := tplExample.ExecuteWriter(pongo2.Context{"query": r.FormValue("query")}, w)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", examplePage)
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
```
|
||||
|
||||
# Benchmark
|
||||
|
||||
The benchmarks have been run on the my machine (`Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz`) using the command:
|
||||
|
||||
go test -bench . -cpu 1,2,4,8
|
||||
|
||||
All benchmarks are compiling (depends on the benchmark) and executing the `template_tests/complex.tpl` template.
|
||||
|
||||
The results are:
|
||||
|
||||
BenchmarkExecuteComplexWithSandboxActive 50000 60450 ns/op
|
||||
BenchmarkExecuteComplexWithSandboxActive-2 50000 56998 ns/op
|
||||
BenchmarkExecuteComplexWithSandboxActive-4 50000 60343 ns/op
|
||||
BenchmarkExecuteComplexWithSandboxActive-8 50000 64229 ns/op
|
||||
BenchmarkCompileAndExecuteComplexWithSandboxActive 10000 164410 ns/op
|
||||
BenchmarkCompileAndExecuteComplexWithSandboxActive-2 10000 156682 ns/op
|
||||
BenchmarkCompileAndExecuteComplexWithSandboxActive-4 10000 164821 ns/op
|
||||
BenchmarkCompileAndExecuteComplexWithSandboxActive-8 10000 171806 ns/op
|
||||
BenchmarkParallelExecuteComplexWithSandboxActive 50000 60428 ns/op
|
||||
BenchmarkParallelExecuteComplexWithSandboxActive-2 50000 31887 ns/op
|
||||
BenchmarkParallelExecuteComplexWithSandboxActive-4 100000 22810 ns/op
|
||||
BenchmarkParallelExecuteComplexWithSandboxActive-8 100000 18820 ns/op
|
||||
BenchmarkExecuteComplexWithoutSandbox 50000 56942 ns/op
|
||||
BenchmarkExecuteComplexWithoutSandbox-2 50000 56168 ns/op
|
||||
BenchmarkExecuteComplexWithoutSandbox-4 50000 57838 ns/op
|
||||
BenchmarkExecuteComplexWithoutSandbox-8 50000 60539 ns/op
|
||||
BenchmarkCompileAndExecuteComplexWithoutSandbox 10000 162086 ns/op
|
||||
BenchmarkCompileAndExecuteComplexWithoutSandbox-2 10000 159771 ns/op
|
||||
BenchmarkCompileAndExecuteComplexWithoutSandbox-4 10000 163826 ns/op
|
||||
BenchmarkCompileAndExecuteComplexWithoutSandbox-8 10000 169062 ns/op
|
||||
BenchmarkParallelExecuteComplexWithoutSandbox 50000 57152 ns/op
|
||||
BenchmarkParallelExecuteComplexWithoutSandbox-2 50000 30276 ns/op
|
||||
BenchmarkParallelExecuteComplexWithoutSandbox-4 100000 22065 ns/op
|
||||
BenchmarkParallelExecuteComplexWithoutSandbox-8 100000 18034 ns/op
|
||||
|
||||
Benchmarked on October 2nd 2014.
|
||||
|
||||
## Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
<a href="https://github.com/flosch/pongo2/graphs/contributors"><img src="https://opencollective.com/pongo2/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
|
||||
## Backers
|
||||
|
||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/pongo2#backer)]
|
||||
|
||||
<a href="https://opencollective.com/pongo2#backers" target="_blank"><img src="https://opencollective.com/pongo2/backers.svg?width=890"></a>
|
||||
|
||||
|
||||
## Sponsors
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/pongo2#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/pongo2/sponsor/0/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pongo2/sponsor/1/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pongo2/sponsor/2/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pongo2/sponsor/3/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pongo2/sponsor/4/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pongo2/sponsor/5/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pongo2/sponsor/6/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pongo2/sponsor/7/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pongo2/sponsor/8/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/pongo2/sponsor/9/website" target="_blank"><img src="https://opencollective.com/pongo2/sponsor/9/avatar.svg"></a>
|
||||
|
||||
|
||||
136
vendor/github.com/flosch/pongo2/context.go
generated
vendored
Normal file
136
vendor/github.com/flosch/pongo2/context.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
||||
|
||||
var autoescape = true
|
||||
|
||||
func SetAutoescape(newValue bool) {
|
||||
autoescape = newValue
|
||||
}
|
||||
|
||||
// A Context type provides constants, variables, instances or functions to a template.
|
||||
//
|
||||
// pongo2 automatically provides meta-information or functions through the "pongo2"-key.
|
||||
// Currently, context["pongo2"] contains the following keys:
|
||||
// 1. version: returns the version string
|
||||
//
|
||||
// Template examples for accessing items from your context:
|
||||
// {{ myconstant }}
|
||||
// {{ myfunc("test", 42) }}
|
||||
// {{ user.name }}
|
||||
// {{ pongo2.version }}
|
||||
type Context map[string]interface{}
|
||||
|
||||
func (c Context) checkForValidIdentifiers() *Error {
|
||||
for k, v := range c {
|
||||
if !reIdentifiers.MatchString(k) {
|
||||
return &Error{
|
||||
Sender: "checkForValidIdentifiers",
|
||||
OrigError: errors.Errorf("context-key '%s' (value: '%+v') is not a valid identifier", k, v),
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates this context with the key/value-pairs from another context.
|
||||
func (c Context) Update(other Context) Context {
|
||||
for k, v := range other {
|
||||
c[k] = v
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ExecutionContext contains all data important for the current rendering state.
|
||||
//
|
||||
// If you're writing a custom tag, your tag's Execute()-function will
|
||||
// have access to the ExecutionContext. This struct stores anything
|
||||
// about the current rendering process's Context including
|
||||
// the Context provided by the user (field Public).
|
||||
// You can safely use the Private context to provide data to the user's
|
||||
// template (like a 'forloop'-information). The Shared-context is used
|
||||
// to share data between tags. All ExecutionContexts share this context.
|
||||
//
|
||||
// Please be careful when accessing the Public data.
|
||||
// PLEASE DO NOT MODIFY THE PUBLIC CONTEXT (read-only).
|
||||
//
|
||||
// To create your own execution context within tags, use the
|
||||
// NewChildExecutionContext(parent) function.
|
||||
type ExecutionContext struct {
|
||||
template *Template
|
||||
|
||||
Autoescape bool
|
||||
Public Context
|
||||
Private Context
|
||||
Shared Context
|
||||
}
|
||||
|
||||
var pongo2MetaContext = Context{
|
||||
"version": Version,
|
||||
}
|
||||
|
||||
func newExecutionContext(tpl *Template, ctx Context) *ExecutionContext {
|
||||
privateCtx := make(Context)
|
||||
|
||||
// Make the pongo2-related funcs/vars available to the context
|
||||
privateCtx["pongo2"] = pongo2MetaContext
|
||||
|
||||
return &ExecutionContext{
|
||||
template: tpl,
|
||||
|
||||
Public: ctx,
|
||||
Private: privateCtx,
|
||||
Autoescape: autoescape,
|
||||
}
|
||||
}
|
||||
|
||||
func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext {
|
||||
newctx := &ExecutionContext{
|
||||
template: parent.template,
|
||||
|
||||
Public: parent.Public,
|
||||
Private: make(Context),
|
||||
Autoescape: parent.Autoescape,
|
||||
}
|
||||
newctx.Shared = parent.Shared
|
||||
|
||||
// Copy all existing private items
|
||||
newctx.Private.Update(parent.Private)
|
||||
|
||||
return newctx
|
||||
}
|
||||
|
||||
func (ctx *ExecutionContext) Error(msg string, token *Token) *Error {
|
||||
return ctx.OrigError(errors.New(msg), token)
|
||||
}
|
||||
|
||||
func (ctx *ExecutionContext) OrigError(err error, token *Token) *Error {
|
||||
filename := ctx.template.name
|
||||
var line, col int
|
||||
if token != nil {
|
||||
// No tokens available
|
||||
// TODO: Add location (from where?)
|
||||
filename = token.Filename
|
||||
line = token.Line
|
||||
col = token.Col
|
||||
}
|
||||
return &Error{
|
||||
Template: ctx.template,
|
||||
Filename: filename,
|
||||
Line: line,
|
||||
Column: col,
|
||||
Token: token,
|
||||
Sender: "execution",
|
||||
OrigError: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *ExecutionContext) Logf(format string, args ...interface{}) {
|
||||
ctx.template.set.logf(format, args...)
|
||||
}
|
||||
31
vendor/github.com/flosch/pongo2/doc.go
generated
vendored
Normal file
31
vendor/github.com/flosch/pongo2/doc.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// A Django-syntax like template-engine
|
||||
//
|
||||
// Blog posts about pongo2 (including introduction and migration):
|
||||
// https://www.florian-schlachter.de/?tag=pongo2
|
||||
//
|
||||
// Complete documentation on the template language:
|
||||
// https://docs.djangoproject.com/en/dev/topics/templates/
|
||||
//
|
||||
// Try out pongo2 live in the pongo2 playground:
|
||||
// https://www.florian-schlachter.de/pongo2/
|
||||
//
|
||||
// Make sure to read README.md in the repository as well.
|
||||
//
|
||||
// A tiny example with template strings:
|
||||
//
|
||||
// (Snippet on playground: https://www.florian-schlachter.de/pongo2/?id=1206546277)
|
||||
//
|
||||
// // Compile the template first (i. e. creating the AST)
|
||||
// tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// // Now you can render the template with the given
|
||||
// // pongo2.Context how often you want to.
|
||||
// out, err := tpl.Execute(pongo2.Context{"name": "fred"})
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// fmt.Println(out) // Output: Hello Fred!
|
||||
//
|
||||
package pongo2
|
||||
91
vendor/github.com/flosch/pongo2/error.go
generated
vendored
Normal file
91
vendor/github.com/flosch/pongo2/error.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// The Error type is being used to address an error during lexing, parsing or
|
||||
// execution. If you want to return an error object (for example in your own
|
||||
// tag or filter) fill this object with as much information as you have.
|
||||
// Make sure "Sender" is always given (if you're returning an error within
|
||||
// a filter, make Sender equals 'filter:yourfilter'; same goes for tags: 'tag:mytag').
|
||||
// It's okay if you only fill in ErrorMsg if you don't have any other details at hand.
|
||||
type Error struct {
|
||||
Template *Template
|
||||
Filename string
|
||||
Line int
|
||||
Column int
|
||||
Token *Token
|
||||
Sender string
|
||||
OrigError error
|
||||
}
|
||||
|
||||
func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error {
|
||||
if e.Template == nil {
|
||||
e.Template = template
|
||||
}
|
||||
|
||||
if e.Token == nil {
|
||||
e.Token = t
|
||||
if e.Line <= 0 {
|
||||
e.Line = t.Line
|
||||
e.Column = t.Col
|
||||
}
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
// Returns a nice formatted error string.
|
||||
func (e *Error) Error() string {
|
||||
s := "[Error"
|
||||
if e.Sender != "" {
|
||||
s += " (where: " + e.Sender + ")"
|
||||
}
|
||||
if e.Filename != "" {
|
||||
s += " in " + e.Filename
|
||||
}
|
||||
if e.Line > 0 {
|
||||
s += fmt.Sprintf(" | Line %d Col %d", e.Line, e.Column)
|
||||
if e.Token != nil {
|
||||
s += fmt.Sprintf(" near '%s'", e.Token.Val)
|
||||
}
|
||||
}
|
||||
s += "] "
|
||||
s += e.OrigError.Error()
|
||||
return s
|
||||
}
|
||||
|
||||
// RawLine returns the affected line from the original template, if available.
|
||||
func (e *Error) RawLine() (line string, available bool, outErr error) {
|
||||
if e.Line <= 0 || e.Filename == "<string>" {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
filename := e.Filename
|
||||
if e.Template != nil {
|
||||
filename = e.Template.set.resolveFilename(e.Template, e.Filename)
|
||||
}
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
defer func() {
|
||||
err := file.Close()
|
||||
if err != nil && outErr == nil {
|
||||
outErr = err
|
||||
}
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
l := 0
|
||||
for scanner.Scan() {
|
||||
l++
|
||||
if l == e.Line {
|
||||
return scanner.Text(), true, nil
|
||||
}
|
||||
}
|
||||
return "", false, nil
|
||||
}
|
||||
143
vendor/github.com/flosch/pongo2/filters.go
generated
vendored
Normal file
143
vendor/github.com/flosch/pongo2/filters.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
// FilterFunction is the type filter functions must fulfil
|
||||
type FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
|
||||
|
||||
var filters map[string]FilterFunction
|
||||
|
||||
func init() {
|
||||
filters = make(map[string]FilterFunction)
|
||||
}
|
||||
|
||||
// FilterExists returns true if the given filter is already registered
|
||||
func FilterExists(name string) bool {
|
||||
_, existing := filters[name]
|
||||
return existing
|
||||
}
|
||||
|
||||
// RegisterFilter registers a new filter. If there's already a filter with the same
|
||||
// name, RegisterFilter will panic. You usually want to call this
|
||||
// function in the filter's init() function:
|
||||
// http://golang.org/doc/effective_go.html#init
|
||||
//
|
||||
// See http://www.florian-schlachter.de/post/pongo2/ for more about
|
||||
// writing filters and tags.
|
||||
func RegisterFilter(name string, fn FilterFunction) error {
|
||||
if FilterExists(name) {
|
||||
return errors.Errorf("filter with name '%s' is already registered", name)
|
||||
}
|
||||
filters[name] = fn
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceFilter replaces an already registered filter with a new implementation. Use this
|
||||
// function with caution since it allows you to change existing filter behaviour.
|
||||
func ReplaceFilter(name string, fn FilterFunction) error {
|
||||
if !FilterExists(name) {
|
||||
return errors.Errorf("filter with name '%s' does not exist (therefore cannot be overridden)", name)
|
||||
}
|
||||
filters[name] = fn
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustApplyFilter behaves like ApplyFilter, but panics on an error.
|
||||
func MustApplyFilter(name string, value *Value, param *Value) *Value {
|
||||
val, err := ApplyFilter(name, value, param)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// ApplyFilter applies a filter to a given value using the given parameters.
|
||||
// Returns a *pongo2.Value or an error.
|
||||
func ApplyFilter(name string, value *Value, param *Value) (*Value, *Error) {
|
||||
fn, existing := filters[name]
|
||||
if !existing {
|
||||
return nil, &Error{
|
||||
Sender: "applyfilter",
|
||||
OrigError: errors.Errorf("Filter with name '%s' not found.", name),
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure param is a *Value
|
||||
if param == nil {
|
||||
param = AsValue(nil)
|
||||
}
|
||||
|
||||
return fn(value, param)
|
||||
}
|
||||
|
||||
type filterCall struct {
|
||||
token *Token
|
||||
|
||||
name string
|
||||
parameter IEvaluator
|
||||
|
||||
filterFunc FilterFunction
|
||||
}
|
||||
|
||||
func (fc *filterCall) Execute(v *Value, ctx *ExecutionContext) (*Value, *Error) {
|
||||
var param *Value
|
||||
var err *Error
|
||||
|
||||
if fc.parameter != nil {
|
||||
param, err = fc.parameter.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
param = AsValue(nil)
|
||||
}
|
||||
|
||||
filteredValue, err := fc.filterFunc(v, param)
|
||||
if err != nil {
|
||||
return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token)
|
||||
}
|
||||
return filteredValue, nil
|
||||
}
|
||||
|
||||
// Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter
|
||||
func (p *Parser) parseFilter() (*filterCall, *Error) {
|
||||
identToken := p.MatchType(TokenIdentifier)
|
||||
|
||||
// Check filter ident
|
||||
if identToken == nil {
|
||||
return nil, p.Error("Filter name must be an identifier.", nil)
|
||||
}
|
||||
|
||||
filter := &filterCall{
|
||||
token: identToken,
|
||||
name: identToken.Val,
|
||||
}
|
||||
|
||||
// Get the appropriate filter function and bind it
|
||||
filterFn, exists := filters[identToken.Val]
|
||||
if !exists {
|
||||
return nil, p.Error(fmt.Sprintf("Filter '%s' does not exist.", identToken.Val), identToken)
|
||||
}
|
||||
|
||||
filter.filterFunc = filterFn
|
||||
|
||||
// Check for filter-argument (2 tokens needed: ':' ARG)
|
||||
if p.Match(TokenSymbol, ":") != nil {
|
||||
if p.Peek(TokenSymbol, "}}") != nil {
|
||||
return nil, p.Error("Filter parameter required after ':'.", nil)
|
||||
}
|
||||
|
||||
// Get filter argument expression
|
||||
v, err := p.parseVariableOrLiteral()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filter.parameter = v
|
||||
}
|
||||
|
||||
return filter, nil
|
||||
}
|
||||
927
vendor/github.com/flosch/pongo2/filters_builtin.go
generated
vendored
Normal file
927
vendor/github.com/flosch/pongo2/filters_builtin.go
generated
vendored
Normal file
@@ -0,0 +1,927 @@
|
||||
package pongo2
|
||||
|
||||
/* Filters that are provided through github.com/flosch/pongo2-addons:
|
||||
------------------------------------------------------------------
|
||||
|
||||
filesizeformat
|
||||
slugify
|
||||
timesince
|
||||
timeuntil
|
||||
|
||||
Filters that won't be added:
|
||||
----------------------------
|
||||
|
||||
get_static_prefix (reason: web-framework specific)
|
||||
pprint (reason: python-specific)
|
||||
static (reason: web-framework specific)
|
||||
|
||||
Reconsideration (not implemented yet):
|
||||
--------------------------------------
|
||||
|
||||
force_escape (reason: not yet needed since this is the behaviour of pongo2's escape filter)
|
||||
safeseq (reason: same reason as `force_escape`)
|
||||
unordered_list (python-specific; not sure whether needed or not)
|
||||
dictsort (python-specific; maybe one could add a filter to sort a list of structs by a specific field name)
|
||||
dictsortreversed (see dictsort)
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
|
||||
RegisterFilter("escape", filterEscape)
|
||||
RegisterFilter("safe", filterSafe)
|
||||
RegisterFilter("escapejs", filterEscapejs)
|
||||
|
||||
RegisterFilter("add", filterAdd)
|
||||
RegisterFilter("addslashes", filterAddslashes)
|
||||
RegisterFilter("capfirst", filterCapfirst)
|
||||
RegisterFilter("center", filterCenter)
|
||||
RegisterFilter("cut", filterCut)
|
||||
RegisterFilter("date", filterDate)
|
||||
RegisterFilter("default", filterDefault)
|
||||
RegisterFilter("default_if_none", filterDefaultIfNone)
|
||||
RegisterFilter("divisibleby", filterDivisibleby)
|
||||
RegisterFilter("first", filterFirst)
|
||||
RegisterFilter("floatformat", filterFloatformat)
|
||||
RegisterFilter("get_digit", filterGetdigit)
|
||||
RegisterFilter("iriencode", filterIriencode)
|
||||
RegisterFilter("join", filterJoin)
|
||||
RegisterFilter("last", filterLast)
|
||||
RegisterFilter("length", filterLength)
|
||||
RegisterFilter("length_is", filterLengthis)
|
||||
RegisterFilter("linebreaks", filterLinebreaks)
|
||||
RegisterFilter("linebreaksbr", filterLinebreaksbr)
|
||||
RegisterFilter("linenumbers", filterLinenumbers)
|
||||
RegisterFilter("ljust", filterLjust)
|
||||
RegisterFilter("lower", filterLower)
|
||||
RegisterFilter("make_list", filterMakelist)
|
||||
RegisterFilter("phone2numeric", filterPhone2numeric)
|
||||
RegisterFilter("pluralize", filterPluralize)
|
||||
RegisterFilter("random", filterRandom)
|
||||
RegisterFilter("removetags", filterRemovetags)
|
||||
RegisterFilter("rjust", filterRjust)
|
||||
RegisterFilter("slice", filterSlice)
|
||||
RegisterFilter("split", filterSplit)
|
||||
RegisterFilter("stringformat", filterStringformat)
|
||||
RegisterFilter("striptags", filterStriptags)
|
||||
RegisterFilter("time", filterDate) // time uses filterDate (same golang-format)
|
||||
RegisterFilter("title", filterTitle)
|
||||
RegisterFilter("truncatechars", filterTruncatechars)
|
||||
RegisterFilter("truncatechars_html", filterTruncatecharsHTML)
|
||||
RegisterFilter("truncatewords", filterTruncatewords)
|
||||
RegisterFilter("truncatewords_html", filterTruncatewordsHTML)
|
||||
RegisterFilter("upper", filterUpper)
|
||||
RegisterFilter("urlencode", filterUrlencode)
|
||||
RegisterFilter("urlize", filterUrlize)
|
||||
RegisterFilter("urlizetrunc", filterUrlizetrunc)
|
||||
RegisterFilter("wordcount", filterWordcount)
|
||||
RegisterFilter("wordwrap", filterWordwrap)
|
||||
RegisterFilter("yesno", filterYesno)
|
||||
|
||||
RegisterFilter("float", filterFloat) // pongo-specific
|
||||
RegisterFilter("integer", filterInteger) // pongo-specific
|
||||
}
|
||||
|
||||
func filterTruncatecharsHelper(s string, newLen int) string {
|
||||
runes := []rune(s)
|
||||
if newLen < len(runes) {
|
||||
if newLen >= 3 {
|
||||
return fmt.Sprintf("%s...", string(runes[:newLen-3]))
|
||||
}
|
||||
// Not enough space for the ellipsis
|
||||
return string(runes[:newLen])
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func filterTruncateHTMLHelper(value string, newOutput *bytes.Buffer, cond func() bool, fn func(c rune, s int, idx int) int, finalize func()) {
|
||||
vLen := len(value)
|
||||
var tagStack []string
|
||||
idx := 0
|
||||
|
||||
for idx < vLen && !cond() {
|
||||
c, s := utf8.DecodeRuneInString(value[idx:])
|
||||
if c == utf8.RuneError {
|
||||
idx += s
|
||||
continue
|
||||
}
|
||||
|
||||
if c == '<' {
|
||||
newOutput.WriteRune(c)
|
||||
idx += s // consume "<"
|
||||
|
||||
if idx+1 < vLen {
|
||||
if value[idx] == '/' {
|
||||
// Close tag
|
||||
|
||||
newOutput.WriteString("/")
|
||||
|
||||
tag := ""
|
||||
idx++ // consume "/"
|
||||
|
||||
for idx < vLen {
|
||||
c2, size2 := utf8.DecodeRuneInString(value[idx:])
|
||||
if c2 == utf8.RuneError {
|
||||
idx += size2
|
||||
continue
|
||||
}
|
||||
|
||||
// End of tag found
|
||||
if c2 == '>' {
|
||||
idx++ // consume ">"
|
||||
break
|
||||
}
|
||||
tag += string(c2)
|
||||
idx += size2
|
||||
}
|
||||
|
||||
if len(tagStack) > 0 {
|
||||
// Ideally, the close tag is TOP of tag stack
|
||||
// In malformed HTML, it must not be, so iterate through the stack and remove the tag
|
||||
for i := len(tagStack) - 1; i >= 0; i-- {
|
||||
if tagStack[i] == tag {
|
||||
// Found the tag
|
||||
tagStack[i] = tagStack[len(tagStack)-1]
|
||||
tagStack = tagStack[:len(tagStack)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newOutput.WriteString(tag)
|
||||
newOutput.WriteString(">")
|
||||
} else {
|
||||
// Open tag
|
||||
|
||||
tag := ""
|
||||
|
||||
params := false
|
||||
for idx < vLen {
|
||||
c2, size2 := utf8.DecodeRuneInString(value[idx:])
|
||||
if c2 == utf8.RuneError {
|
||||
idx += size2
|
||||
continue
|
||||
}
|
||||
|
||||
newOutput.WriteRune(c2)
|
||||
|
||||
// End of tag found
|
||||
if c2 == '>' {
|
||||
idx++ // consume ">"
|
||||
break
|
||||
}
|
||||
|
||||
if !params {
|
||||
if c2 == ' ' {
|
||||
params = true
|
||||
} else {
|
||||
tag += string(c2)
|
||||
}
|
||||
}
|
||||
|
||||
idx += size2
|
||||
}
|
||||
|
||||
// Add tag to stack
|
||||
tagStack = append(tagStack, tag)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
idx = fn(c, s, idx)
|
||||
}
|
||||
}
|
||||
|
||||
finalize()
|
||||
|
||||
for i := len(tagStack) - 1; i >= 0; i-- {
|
||||
tag := tagStack[i]
|
||||
// Close everything from the regular tag stack
|
||||
newOutput.WriteString(fmt.Sprintf("</%s>", tag))
|
||||
}
|
||||
}
|
||||
|
||||
func filterTruncatechars(in *Value, param *Value) (*Value, *Error) {
|
||||
s := in.String()
|
||||
newLen := param.Integer()
|
||||
return AsValue(filterTruncatecharsHelper(s, newLen)), nil
|
||||
}
|
||||
|
||||
func filterTruncatecharsHTML(in *Value, param *Value) (*Value, *Error) {
|
||||
value := in.String()
|
||||
newLen := max(param.Integer()-3, 0)
|
||||
|
||||
newOutput := bytes.NewBuffer(nil)
|
||||
|
||||
textcounter := 0
|
||||
|
||||
filterTruncateHTMLHelper(value, newOutput, func() bool {
|
||||
return textcounter >= newLen
|
||||
}, func(c rune, s int, idx int) int {
|
||||
textcounter++
|
||||
newOutput.WriteRune(c)
|
||||
|
||||
return idx + s
|
||||
}, func() {
|
||||
if textcounter >= newLen && textcounter < len(value) {
|
||||
newOutput.WriteString("...")
|
||||
}
|
||||
})
|
||||
|
||||
return AsSafeValue(newOutput.String()), nil
|
||||
}
|
||||
|
||||
func filterTruncatewords(in *Value, param *Value) (*Value, *Error) {
|
||||
words := strings.Fields(in.String())
|
||||
n := param.Integer()
|
||||
if n <= 0 {
|
||||
return AsValue(""), nil
|
||||
}
|
||||
nlen := min(len(words), n)
|
||||
out := make([]string, 0, nlen)
|
||||
for i := 0; i < nlen; i++ {
|
||||
out = append(out, words[i])
|
||||
}
|
||||
|
||||
if n < len(words) {
|
||||
out = append(out, "...")
|
||||
}
|
||||
|
||||
return AsValue(strings.Join(out, " ")), nil
|
||||
}
|
||||
|
||||
func filterTruncatewordsHTML(in *Value, param *Value) (*Value, *Error) {
|
||||
value := in.String()
|
||||
newLen := max(param.Integer(), 0)
|
||||
|
||||
newOutput := bytes.NewBuffer(nil)
|
||||
|
||||
wordcounter := 0
|
||||
|
||||
filterTruncateHTMLHelper(value, newOutput, func() bool {
|
||||
return wordcounter >= newLen
|
||||
}, func(_ rune, _ int, idx int) int {
|
||||
// Get next word
|
||||
wordFound := false
|
||||
|
||||
for idx < len(value) {
|
||||
c2, size2 := utf8.DecodeRuneInString(value[idx:])
|
||||
if c2 == utf8.RuneError {
|
||||
idx += size2
|
||||
continue
|
||||
}
|
||||
|
||||
if c2 == '<' {
|
||||
// HTML tag start, don't consume it
|
||||
return idx
|
||||
}
|
||||
|
||||
newOutput.WriteRune(c2)
|
||||
idx += size2
|
||||
|
||||
if c2 == ' ' || c2 == '.' || c2 == ',' || c2 == ';' {
|
||||
// Word ends here, stop capturing it now
|
||||
break
|
||||
} else {
|
||||
wordFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if wordFound {
|
||||
wordcounter++
|
||||
}
|
||||
|
||||
return idx
|
||||
}, func() {
|
||||
if wordcounter >= newLen {
|
||||
newOutput.WriteString("...")
|
||||
}
|
||||
})
|
||||
|
||||
return AsSafeValue(newOutput.String()), nil
|
||||
}
|
||||
|
||||
func filterEscape(in *Value, param *Value) (*Value, *Error) {
|
||||
output := strings.Replace(in.String(), "&", "&", -1)
|
||||
output = strings.Replace(output, ">", ">", -1)
|
||||
output = strings.Replace(output, "<", "<", -1)
|
||||
output = strings.Replace(output, "\"", """, -1)
|
||||
output = strings.Replace(output, "'", "'", -1)
|
||||
return AsValue(output), nil
|
||||
}
|
||||
|
||||
func filterSafe(in *Value, param *Value) (*Value, *Error) {
|
||||
return in, nil // nothing to do here, just to keep track of the safe application
|
||||
}
|
||||
|
||||
func filterEscapejs(in *Value, param *Value) (*Value, *Error) {
|
||||
sin := in.String()
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
idx := 0
|
||||
for idx < len(sin) {
|
||||
c, size := utf8.DecodeRuneInString(sin[idx:])
|
||||
if c == utf8.RuneError {
|
||||
idx += size
|
||||
continue
|
||||
}
|
||||
|
||||
if c == '\\' {
|
||||
// Escape seq?
|
||||
if idx+1 < len(sin) {
|
||||
switch sin[idx+1] {
|
||||
case 'r':
|
||||
b.WriteString(fmt.Sprintf(`\u%04X`, '\r'))
|
||||
idx += 2
|
||||
continue
|
||||
case 'n':
|
||||
b.WriteString(fmt.Sprintf(`\u%04X`, '\n'))
|
||||
idx += 2
|
||||
continue
|
||||
/*case '\'':
|
||||
b.WriteString(fmt.Sprintf(`\u%04X`, '\''))
|
||||
idx += 2
|
||||
continue
|
||||
case '"':
|
||||
b.WriteString(fmt.Sprintf(`\u%04X`, '"'))
|
||||
idx += 2
|
||||
continue*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == ' ' || c == '/' {
|
||||
b.WriteRune(c)
|
||||
} else {
|
||||
b.WriteString(fmt.Sprintf(`\u%04X`, c))
|
||||
}
|
||||
|
||||
idx += size
|
||||
}
|
||||
|
||||
return AsValue(b.String()), nil
|
||||
}
|
||||
|
||||
func filterAdd(in *Value, param *Value) (*Value, *Error) {
|
||||
if in.IsNumber() && param.IsNumber() {
|
||||
if in.IsFloat() || param.IsFloat() {
|
||||
return AsValue(in.Float() + param.Float()), nil
|
||||
}
|
||||
return AsValue(in.Integer() + param.Integer()), nil
|
||||
}
|
||||
// If in/param is not a number, we're relying on the
|
||||
// Value's String() conversion and just add them both together
|
||||
return AsValue(in.String() + param.String()), nil
|
||||
}
|
||||
|
||||
func filterAddslashes(in *Value, param *Value) (*Value, *Error) {
|
||||
output := strings.Replace(in.String(), "\\", "\\\\", -1)
|
||||
output = strings.Replace(output, "\"", "\\\"", -1)
|
||||
output = strings.Replace(output, "'", "\\'", -1)
|
||||
return AsValue(output), nil
|
||||
}
|
||||
|
||||
func filterCut(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(strings.Replace(in.String(), param.String(), "", -1)), nil
|
||||
}
|
||||
|
||||
func filterLength(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(in.Len()), nil
|
||||
}
|
||||
|
||||
func filterLengthis(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(in.Len() == param.Integer()), nil
|
||||
}
|
||||
|
||||
func filterDefault(in *Value, param *Value) (*Value, *Error) {
|
||||
if !in.IsTrue() {
|
||||
return param, nil
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func filterDefaultIfNone(in *Value, param *Value) (*Value, *Error) {
|
||||
if in.IsNil() {
|
||||
return param, nil
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func filterDivisibleby(in *Value, param *Value) (*Value, *Error) {
|
||||
if param.Integer() == 0 {
|
||||
return AsValue(false), nil
|
||||
}
|
||||
return AsValue(in.Integer()%param.Integer() == 0), nil
|
||||
}
|
||||
|
||||
func filterFirst(in *Value, param *Value) (*Value, *Error) {
|
||||
if in.CanSlice() && in.Len() > 0 {
|
||||
return in.Index(0), nil
|
||||
}
|
||||
return AsValue(""), nil
|
||||
}
|
||||
|
||||
func filterFloatformat(in *Value, param *Value) (*Value, *Error) {
|
||||
val := in.Float()
|
||||
|
||||
decimals := -1
|
||||
if !param.IsNil() {
|
||||
// Any argument provided?
|
||||
decimals = param.Integer()
|
||||
}
|
||||
|
||||
// if the argument is not a number (e. g. empty), the default
|
||||
// behaviour is trim the result
|
||||
trim := !param.IsNumber()
|
||||
|
||||
if decimals <= 0 {
|
||||
// argument is negative or zero, so we
|
||||
// want the output being trimmed
|
||||
decimals = -decimals
|
||||
trim = true
|
||||
}
|
||||
|
||||
if trim {
|
||||
// Remove zeroes
|
||||
if float64(int(val)) == val {
|
||||
return AsValue(in.Integer()), nil
|
||||
}
|
||||
}
|
||||
|
||||
return AsValue(strconv.FormatFloat(val, 'f', decimals, 64)), nil
|
||||
}
|
||||
|
||||
func filterGetdigit(in *Value, param *Value) (*Value, *Error) {
|
||||
i := param.Integer()
|
||||
l := len(in.String()) // do NOT use in.Len() here!
|
||||
if i <= 0 || i > l {
|
||||
return in, nil
|
||||
}
|
||||
return AsValue(in.String()[l-i] - 48), nil
|
||||
}
|
||||
|
||||
const filterIRIChars = "/#%[]=:;$&()+,!?*@'~"
|
||||
|
||||
func filterIriencode(in *Value, param *Value) (*Value, *Error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
sin := in.String()
|
||||
for _, r := range sin {
|
||||
if strings.IndexRune(filterIRIChars, r) >= 0 {
|
||||
b.WriteRune(r)
|
||||
} else {
|
||||
b.WriteString(url.QueryEscape(string(r)))
|
||||
}
|
||||
}
|
||||
|
||||
return AsValue(b.String()), nil
|
||||
}
|
||||
|
||||
func filterJoin(in *Value, param *Value) (*Value, *Error) {
|
||||
if !in.CanSlice() {
|
||||
return in, nil
|
||||
}
|
||||
sep := param.String()
|
||||
sl := make([]string, 0, in.Len())
|
||||
for i := 0; i < in.Len(); i++ {
|
||||
sl = append(sl, in.Index(i).String())
|
||||
}
|
||||
return AsValue(strings.Join(sl, sep)), nil
|
||||
}
|
||||
|
||||
func filterLast(in *Value, param *Value) (*Value, *Error) {
|
||||
if in.CanSlice() && in.Len() > 0 {
|
||||
return in.Index(in.Len() - 1), nil
|
||||
}
|
||||
return AsValue(""), nil
|
||||
}
|
||||
|
||||
func filterUpper(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(strings.ToUpper(in.String())), nil
|
||||
}
|
||||
|
||||
func filterLower(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(strings.ToLower(in.String())), nil
|
||||
}
|
||||
|
||||
func filterMakelist(in *Value, param *Value) (*Value, *Error) {
|
||||
s := in.String()
|
||||
result := make([]string, 0, len(s))
|
||||
for _, c := range s {
|
||||
result = append(result, string(c))
|
||||
}
|
||||
return AsValue(result), nil
|
||||
}
|
||||
|
||||
func filterCapfirst(in *Value, param *Value) (*Value, *Error) {
|
||||
if in.Len() <= 0 {
|
||||
return AsValue(""), nil
|
||||
}
|
||||
t := in.String()
|
||||
r, size := utf8.DecodeRuneInString(t)
|
||||
return AsValue(strings.ToUpper(string(r)) + t[size:]), nil
|
||||
}
|
||||
|
||||
func filterCenter(in *Value, param *Value) (*Value, *Error) {
|
||||
width := param.Integer()
|
||||
slen := in.Len()
|
||||
if width <= slen {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
spaces := width - slen
|
||||
left := spaces/2 + spaces%2
|
||||
right := spaces / 2
|
||||
|
||||
return AsValue(fmt.Sprintf("%s%s%s", strings.Repeat(" ", left),
|
||||
in.String(), strings.Repeat(" ", right))), nil
|
||||
}
|
||||
|
||||
func filterDate(in *Value, param *Value) (*Value, *Error) {
|
||||
t, isTime := in.Interface().(time.Time)
|
||||
if !isTime {
|
||||
return nil, &Error{
|
||||
Sender: "filter:date",
|
||||
OrigError: errors.New("filter input argument must be of type 'time.Time'"),
|
||||
}
|
||||
}
|
||||
return AsValue(t.Format(param.String())), nil
|
||||
}
|
||||
|
||||
func filterFloat(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(in.Float()), nil
|
||||
}
|
||||
|
||||
func filterInteger(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(in.Integer()), nil
|
||||
}
|
||||
|
||||
func filterLinebreaks(in *Value, param *Value) (*Value, *Error) {
|
||||
if in.Len() == 0 {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
// Newline = <br />
|
||||
// Double newline = <p>...</p>
|
||||
lines := strings.Split(in.String(), "\n")
|
||||
lenlines := len(lines)
|
||||
|
||||
opened := false
|
||||
|
||||
for idx, line := range lines {
|
||||
|
||||
if !opened {
|
||||
b.WriteString("<p>")
|
||||
opened = true
|
||||
}
|
||||
|
||||
b.WriteString(line)
|
||||
|
||||
if idx < lenlines-1 && strings.TrimSpace(lines[idx]) != "" {
|
||||
// We've not reached the end
|
||||
if strings.TrimSpace(lines[idx+1]) == "" {
|
||||
// Next line is empty
|
||||
if opened {
|
||||
b.WriteString("</p>")
|
||||
opened = false
|
||||
}
|
||||
} else {
|
||||
b.WriteString("<br />")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opened {
|
||||
b.WriteString("</p>")
|
||||
}
|
||||
|
||||
return AsValue(b.String()), nil
|
||||
}
|
||||
|
||||
func filterSplit(in *Value, param *Value) (*Value, *Error) {
|
||||
chunks := strings.Split(in.String(), param.String())
|
||||
|
||||
return AsValue(chunks), nil
|
||||
}
|
||||
|
||||
func filterLinebreaksbr(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(strings.Replace(in.String(), "\n", "<br />", -1)), nil
|
||||
}
|
||||
|
||||
func filterLinenumbers(in *Value, param *Value) (*Value, *Error) {
|
||||
lines := strings.Split(in.String(), "\n")
|
||||
output := make([]string, 0, len(lines))
|
||||
for idx, line := range lines {
|
||||
output = append(output, fmt.Sprintf("%d. %s", idx+1, line))
|
||||
}
|
||||
return AsValue(strings.Join(output, "\n")), nil
|
||||
}
|
||||
|
||||
func filterLjust(in *Value, param *Value) (*Value, *Error) {
|
||||
times := param.Integer() - in.Len()
|
||||
if times < 0 {
|
||||
times = 0
|
||||
}
|
||||
return AsValue(fmt.Sprintf("%s%s", in.String(), strings.Repeat(" ", times))), nil
|
||||
}
|
||||
|
||||
func filterUrlencode(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(url.QueryEscape(in.String())), nil
|
||||
}
|
||||
|
||||
// TODO: This regexp could do some work
|
||||
var filterUrlizeURLRegexp = regexp.MustCompile(`((((http|https)://)|www\.|((^|[ ])[0-9A-Za-z_\-]+(\.com|\.net|\.org|\.info|\.biz|\.de))))(?U:.*)([ ]+|$)`)
|
||||
var filterUrlizeEmailRegexp = regexp.MustCompile(`(\w+@\w+\.\w{2,4})`)
|
||||
|
||||
func filterUrlizeHelper(input string, autoescape bool, trunc int) (string, error) {
|
||||
var soutErr error
|
||||
sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string {
|
||||
var prefix string
|
||||
var suffix string
|
||||
if strings.HasPrefix(raw_url, " ") {
|
||||
prefix = " "
|
||||
}
|
||||
if strings.HasSuffix(raw_url, " ") {
|
||||
suffix = " "
|
||||
}
|
||||
|
||||
raw_url = strings.TrimSpace(raw_url)
|
||||
|
||||
t, err := ApplyFilter("iriencode", AsValue(raw_url), nil)
|
||||
if err != nil {
|
||||
soutErr = err
|
||||
return ""
|
||||
}
|
||||
url := t.String()
|
||||
|
||||
if !strings.HasPrefix(url, "http") {
|
||||
url = fmt.Sprintf("http://%s", url)
|
||||
}
|
||||
|
||||
title := raw_url
|
||||
|
||||
if trunc > 3 && len(title) > trunc {
|
||||
title = fmt.Sprintf("%s...", title[:trunc-3])
|
||||
}
|
||||
|
||||
if autoescape {
|
||||
t, err := ApplyFilter("escape", AsValue(title), nil)
|
||||
if err != nil {
|
||||
soutErr = err
|
||||
return ""
|
||||
}
|
||||
title = t.String()
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`%s<a href="%s" rel="nofollow">%s</a>%s`, prefix, url, title, suffix)
|
||||
})
|
||||
if soutErr != nil {
|
||||
return "", soutErr
|
||||
}
|
||||
|
||||
sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string {
|
||||
title := mail
|
||||
|
||||
if trunc > 3 && len(title) > trunc {
|
||||
title = fmt.Sprintf("%s...", title[:trunc-3])
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`<a href="mailto:%s">%s</a>`, mail, title)
|
||||
})
|
||||
|
||||
return sout, nil
|
||||
}
|
||||
|
||||
func filterUrlize(in *Value, param *Value) (*Value, *Error) {
|
||||
autoescape := true
|
||||
if param.IsBool() {
|
||||
autoescape = param.Bool()
|
||||
}
|
||||
|
||||
s, err := filterUrlizeHelper(in.String(), autoescape, -1)
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
|
||||
return AsValue(s), nil
|
||||
}
|
||||
|
||||
func filterUrlizetrunc(in *Value, param *Value) (*Value, *Error) {
|
||||
s, err := filterUrlizeHelper(in.String(), true, param.Integer())
|
||||
if err != nil {
|
||||
return nil, &Error{
|
||||
Sender: "filter:urlizetrunc",
|
||||
OrigError: errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"),
|
||||
}
|
||||
}
|
||||
return AsValue(s), nil
|
||||
}
|
||||
|
||||
func filterStringformat(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(fmt.Sprintf(param.String(), in.Interface())), nil
|
||||
}
|
||||
|
||||
var reStriptags = regexp.MustCompile("<[^>]*?>")
|
||||
|
||||
func filterStriptags(in *Value, param *Value) (*Value, *Error) {
|
||||
s := in.String()
|
||||
|
||||
// Strip all tags
|
||||
s = reStriptags.ReplaceAllString(s, "")
|
||||
|
||||
return AsValue(strings.TrimSpace(s)), nil
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Phoneword
|
||||
var filterPhone2numericMap = map[string]string{
|
||||
"a": "2", "b": "2", "c": "2", "d": "3", "e": "3", "f": "3", "g": "4", "h": "4", "i": "4", "j": "5", "k": "5",
|
||||
"l": "5", "m": "6", "n": "6", "o": "6", "p": "7", "q": "7", "r": "7", "s": "7", "t": "8", "u": "8", "v": "8",
|
||||
"w": "9", "x": "9", "y": "9", "z": "9",
|
||||
}
|
||||
|
||||
func filterPhone2numeric(in *Value, param *Value) (*Value, *Error) {
|
||||
sin := in.String()
|
||||
for k, v := range filterPhone2numericMap {
|
||||
sin = strings.Replace(sin, k, v, -1)
|
||||
sin = strings.Replace(sin, strings.ToUpper(k), v, -1)
|
||||
}
|
||||
return AsValue(sin), nil
|
||||
}
|
||||
|
||||
func filterPluralize(in *Value, param *Value) (*Value, *Error) {
|
||||
if in.IsNumber() {
|
||||
// Works only on numbers
|
||||
if param.Len() > 0 {
|
||||
endings := strings.Split(param.String(), ",")
|
||||
if len(endings) > 2 {
|
||||
return nil, &Error{
|
||||
Sender: "filter:pluralize",
|
||||
OrigError: errors.New("you cannot pass more than 2 arguments to filter 'pluralize'"),
|
||||
}
|
||||
}
|
||||
if len(endings) == 1 {
|
||||
// 1 argument
|
||||
if in.Integer() != 1 {
|
||||
return AsValue(endings[0]), nil
|
||||
}
|
||||
} else {
|
||||
if in.Integer() != 1 {
|
||||
// 2 arguments
|
||||
return AsValue(endings[1]), nil
|
||||
}
|
||||
return AsValue(endings[0]), nil
|
||||
}
|
||||
} else {
|
||||
if in.Integer() != 1 {
|
||||
// return default 's'
|
||||
return AsValue("s"), nil
|
||||
}
|
||||
}
|
||||
|
||||
return AsValue(""), nil
|
||||
}
|
||||
return nil, &Error{
|
||||
Sender: "filter:pluralize",
|
||||
OrigError: errors.New("filter 'pluralize' does only work on numbers"),
|
||||
}
|
||||
}
|
||||
|
||||
func filterRandom(in *Value, param *Value) (*Value, *Error) {
|
||||
if !in.CanSlice() || in.Len() <= 0 {
|
||||
return in, nil
|
||||
}
|
||||
i := rand.Intn(in.Len())
|
||||
return in.Index(i), nil
|
||||
}
|
||||
|
||||
func filterRemovetags(in *Value, param *Value) (*Value, *Error) {
|
||||
s := in.String()
|
||||
tags := strings.Split(param.String(), ",")
|
||||
|
||||
// Strip only specific tags
|
||||
for _, tag := range tags {
|
||||
re := regexp.MustCompile(fmt.Sprintf("</?%s/?>", tag))
|
||||
s = re.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
return AsValue(strings.TrimSpace(s)), nil
|
||||
}
|
||||
|
||||
func filterRjust(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(fmt.Sprintf(fmt.Sprintf("%%%ds", param.Integer()), in.String())), nil
|
||||
}
|
||||
|
||||
func filterSlice(in *Value, param *Value) (*Value, *Error) {
|
||||
comp := strings.Split(param.String(), ":")
|
||||
if len(comp) != 2 {
|
||||
return nil, &Error{
|
||||
Sender: "filter:slice",
|
||||
OrigError: errors.New("Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]"),
|
||||
}
|
||||
}
|
||||
|
||||
if !in.CanSlice() {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
from := AsValue(comp[0]).Integer()
|
||||
to := in.Len()
|
||||
|
||||
if from > to {
|
||||
from = to
|
||||
}
|
||||
|
||||
vto := AsValue(comp[1]).Integer()
|
||||
if vto >= from && vto <= in.Len() {
|
||||
to = vto
|
||||
}
|
||||
|
||||
return in.Slice(from, to), nil
|
||||
}
|
||||
|
||||
func filterTitle(in *Value, param *Value) (*Value, *Error) {
|
||||
if !in.IsString() {
|
||||
return AsValue(""), nil
|
||||
}
|
||||
return AsValue(strings.Title(strings.ToLower(in.String()))), nil
|
||||
}
|
||||
|
||||
func filterWordcount(in *Value, param *Value) (*Value, *Error) {
|
||||
return AsValue(len(strings.Fields(in.String()))), nil
|
||||
}
|
||||
|
||||
func filterWordwrap(in *Value, param *Value) (*Value, *Error) {
|
||||
words := strings.Fields(in.String())
|
||||
wordsLen := len(words)
|
||||
wrapAt := param.Integer()
|
||||
if wrapAt <= 0 {
|
||||
return in, nil
|
||||
}
|
||||
|
||||
linecount := wordsLen/wrapAt + wordsLen%wrapAt
|
||||
lines := make([]string, 0, linecount)
|
||||
for i := 0; i < linecount; i++ {
|
||||
lines = append(lines, strings.Join(words[wrapAt*i:min(wrapAt*(i+1), wordsLen)], " "))
|
||||
}
|
||||
return AsValue(strings.Join(lines, "\n")), nil
|
||||
}
|
||||
|
||||
func filterYesno(in *Value, param *Value) (*Value, *Error) {
|
||||
choices := map[int]string{
|
||||
0: "yes",
|
||||
1: "no",
|
||||
2: "maybe",
|
||||
}
|
||||
paramString := param.String()
|
||||
customChoices := strings.Split(paramString, ",")
|
||||
if len(paramString) > 0 {
|
||||
if len(customChoices) > 3 {
|
||||
return nil, &Error{
|
||||
Sender: "filter:yesno",
|
||||
OrigError: errors.Errorf("You cannot pass more than 3 options to the 'yesno'-filter (got: '%s').", paramString),
|
||||
}
|
||||
}
|
||||
if len(customChoices) < 2 {
|
||||
return nil, &Error{
|
||||
Sender: "filter:yesno",
|
||||
OrigError: errors.Errorf("You must pass either no or at least 2 arguments to the 'yesno'-filter (got: '%s').", paramString),
|
||||
}
|
||||
}
|
||||
|
||||
// Map to the options now
|
||||
choices[0] = customChoices[0]
|
||||
choices[1] = customChoices[1]
|
||||
if len(customChoices) == 3 {
|
||||
choices[2] = customChoices[2]
|
||||
}
|
||||
}
|
||||
|
||||
// maybe
|
||||
if in.IsNil() {
|
||||
return AsValue(choices[2]), nil
|
||||
}
|
||||
|
||||
// yes
|
||||
if in.IsTrue() {
|
||||
return AsValue(choices[0]), nil
|
||||
}
|
||||
|
||||
// no
|
||||
return AsValue(choices[1]), nil
|
||||
}
|
||||
13
vendor/github.com/flosch/pongo2/go.mod
generated
vendored
Normal file
13
vendor/github.com/flosch/pongo2/go.mod
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
module github.com/flosch/pongo2
|
||||
|
||||
require (
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127
|
||||
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5
|
||||
github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect
|
||||
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/mattn/goveralls v0.0.2 // indirect
|
||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207 // indirect
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
)
|
||||
15
vendor/github.com/flosch/pongo2/helpers.go
generated
vendored
Normal file
15
vendor/github.com/flosch/pongo2/helpers.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package pongo2
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
432
vendor/github.com/flosch/pongo2/lexer.go
generated
vendored
Normal file
432
vendor/github.com/flosch/pongo2/lexer.go
generated
vendored
Normal file
@@ -0,0 +1,432 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenError = iota
|
||||
EOF
|
||||
|
||||
TokenHTML
|
||||
|
||||
TokenKeyword
|
||||
TokenIdentifier
|
||||
TokenString
|
||||
TokenNumber
|
||||
TokenSymbol
|
||||
)
|
||||
|
||||
var (
|
||||
tokenSpaceChars = " \n\r\t"
|
||||
tokenIdentifierChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
|
||||
tokenIdentifierCharsWithDigits = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"
|
||||
tokenDigits = "0123456789"
|
||||
|
||||
// Available symbols in pongo2 (within filters/tag)
|
||||
TokenSymbols = []string{
|
||||
// 3-Char symbols
|
||||
"{{-", "-}}", "{%-", "-%}",
|
||||
|
||||
// 2-Char symbols
|
||||
"==", ">=", "<=", "&&", "||", "{{", "}}", "{%", "%}", "!=", "<>",
|
||||
|
||||
// 1-Char symbol
|
||||
"(", ")", "+", "-", "*", "<", ">", "/", "^", ",", ".", "!", "|", ":", "=", "%",
|
||||
}
|
||||
|
||||
// Available keywords in pongo2
|
||||
TokenKeywords = []string{"in", "and", "or", "not", "true", "false", "as", "export"}
|
||||
)
|
||||
|
||||
type TokenType int
|
||||
type Token struct {
|
||||
Filename string
|
||||
Typ TokenType
|
||||
Val string
|
||||
Line int
|
||||
Col int
|
||||
TrimWhitespaces bool
|
||||
}
|
||||
|
||||
type lexerStateFn func() lexerStateFn
|
||||
type lexer struct {
|
||||
name string
|
||||
input string
|
||||
start int // start pos of the item
|
||||
pos int // current pos
|
||||
width int // width of last rune
|
||||
tokens []*Token
|
||||
errored bool
|
||||
startline int
|
||||
startcol int
|
||||
line int
|
||||
col int
|
||||
|
||||
inVerbatim bool
|
||||
verbatimName string
|
||||
}
|
||||
|
||||
func (t *Token) String() string {
|
||||
val := t.Val
|
||||
if len(val) > 1000 {
|
||||
val = fmt.Sprintf("%s...%s", val[:10], val[len(val)-5:len(val)])
|
||||
}
|
||||
|
||||
typ := ""
|
||||
switch t.Typ {
|
||||
case TokenHTML:
|
||||
typ = "HTML"
|
||||
case TokenError:
|
||||
typ = "Error"
|
||||
case TokenIdentifier:
|
||||
typ = "Identifier"
|
||||
case TokenKeyword:
|
||||
typ = "Keyword"
|
||||
case TokenNumber:
|
||||
typ = "Number"
|
||||
case TokenString:
|
||||
typ = "String"
|
||||
case TokenSymbol:
|
||||
typ = "Symbol"
|
||||
default:
|
||||
typ = "Unknown"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("<Token Typ=%s (%d) Val='%s' Line=%d Col=%d, WT=%t>",
|
||||
typ, t.Typ, val, t.Line, t.Col, t.TrimWhitespaces)
|
||||
}
|
||||
|
||||
func lex(name string, input string) ([]*Token, *Error) {
|
||||
l := &lexer{
|
||||
name: name,
|
||||
input: input,
|
||||
tokens: make([]*Token, 0, 100),
|
||||
line: 1,
|
||||
col: 1,
|
||||
startline: 1,
|
||||
startcol: 1,
|
||||
}
|
||||
l.run()
|
||||
if l.errored {
|
||||
errtoken := l.tokens[len(l.tokens)-1]
|
||||
return nil, &Error{
|
||||
Filename: name,
|
||||
Line: errtoken.Line,
|
||||
Column: errtoken.Col,
|
||||
Sender: "lexer",
|
||||
OrigError: errors.New(errtoken.Val),
|
||||
}
|
||||
}
|
||||
return l.tokens, nil
|
||||
}
|
||||
|
||||
func (l *lexer) value() string {
|
||||
return l.input[l.start:l.pos]
|
||||
}
|
||||
|
||||
func (l *lexer) length() int {
|
||||
return l.pos - l.start
|
||||
}
|
||||
|
||||
func (l *lexer) emit(t TokenType) {
|
||||
tok := &Token{
|
||||
Filename: l.name,
|
||||
Typ: t,
|
||||
Val: l.value(),
|
||||
Line: l.startline,
|
||||
Col: l.startcol,
|
||||
}
|
||||
|
||||
if t == TokenString {
|
||||
// Escape sequence \" in strings
|
||||
tok.Val = strings.Replace(tok.Val, `\"`, `"`, -1)
|
||||
tok.Val = strings.Replace(tok.Val, `\\`, `\`, -1)
|
||||
}
|
||||
|
||||
if t == TokenSymbol && len(tok.Val) == 3 && (strings.HasSuffix(tok.Val, "-") || strings.HasPrefix(tok.Val, "-")) {
|
||||
tok.TrimWhitespaces = true
|
||||
tok.Val = strings.Replace(tok.Val, "-", "", -1)
|
||||
}
|
||||
|
||||
l.tokens = append(l.tokens, tok)
|
||||
l.start = l.pos
|
||||
l.startline = l.line
|
||||
l.startcol = l.col
|
||||
}
|
||||
|
||||
func (l *lexer) next() rune {
|
||||
if l.pos >= len(l.input) {
|
||||
l.width = 0
|
||||
return EOF
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = w
|
||||
l.pos += l.width
|
||||
l.col += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
l.col -= l.width
|
||||
}
|
||||
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
l.startline = l.line
|
||||
l.startcol = l.col
|
||||
}
|
||||
|
||||
func (l *lexer) accept(what string) bool {
|
||||
if strings.IndexRune(what, l.next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *lexer) acceptRun(what string) {
|
||||
for strings.IndexRune(what, l.next()) >= 0 {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
func (l *lexer) errorf(format string, args ...interface{}) lexerStateFn {
|
||||
t := &Token{
|
||||
Filename: l.name,
|
||||
Typ: TokenError,
|
||||
Val: fmt.Sprintf(format, args...),
|
||||
Line: l.startline,
|
||||
Col: l.startcol,
|
||||
}
|
||||
l.tokens = append(l.tokens, t)
|
||||
l.errored = true
|
||||
l.startline = l.line
|
||||
l.startcol = l.col
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lexer) eof() bool {
|
||||
return l.start >= len(l.input)-1
|
||||
}
|
||||
|
||||
func (l *lexer) run() {
|
||||
for {
|
||||
// TODO: Support verbatim tag names
|
||||
// https://docs.djangoproject.com/en/dev/ref/templates/builtins/#verbatim
|
||||
if l.inVerbatim {
|
||||
name := l.verbatimName
|
||||
if name != "" {
|
||||
name += " "
|
||||
}
|
||||
if strings.HasPrefix(l.input[l.pos:], fmt.Sprintf("{%% endverbatim %s%%}", name)) { // end verbatim
|
||||
if l.pos > l.start {
|
||||
l.emit(TokenHTML)
|
||||
}
|
||||
w := len("{% endverbatim %}")
|
||||
l.pos += w
|
||||
l.col += w
|
||||
l.ignore()
|
||||
l.inVerbatim = false
|
||||
}
|
||||
} else if strings.HasPrefix(l.input[l.pos:], "{% verbatim %}") { // tag
|
||||
if l.pos > l.start {
|
||||
l.emit(TokenHTML)
|
||||
}
|
||||
l.inVerbatim = true
|
||||
w := len("{% verbatim %}")
|
||||
l.pos += w
|
||||
l.col += w
|
||||
l.ignore()
|
||||
}
|
||||
|
||||
if !l.inVerbatim {
|
||||
// Ignore single-line comments {# ... #}
|
||||
if strings.HasPrefix(l.input[l.pos:], "{#") {
|
||||
if l.pos > l.start {
|
||||
l.emit(TokenHTML)
|
||||
}
|
||||
|
||||
l.pos += 2 // pass '{#'
|
||||
l.col += 2
|
||||
|
||||
for {
|
||||
switch l.peek() {
|
||||
case EOF:
|
||||
l.errorf("Single-line comment not closed.")
|
||||
return
|
||||
case '\n':
|
||||
l.errorf("Newline not permitted in a single-line comment.")
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(l.input[l.pos:], "#}") {
|
||||
l.pos += 2 // pass '#}'
|
||||
l.col += 2
|
||||
break
|
||||
}
|
||||
|
||||
l.next()
|
||||
}
|
||||
l.ignore() // ignore whole comment
|
||||
|
||||
// Comment skipped
|
||||
continue // next token
|
||||
}
|
||||
|
||||
if strings.HasPrefix(l.input[l.pos:], "{{") || // variable
|
||||
strings.HasPrefix(l.input[l.pos:], "{%") { // tag
|
||||
if l.pos > l.start {
|
||||
l.emit(TokenHTML)
|
||||
}
|
||||
l.tokenize()
|
||||
if l.errored {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch l.peek() {
|
||||
case '\n':
|
||||
l.line++
|
||||
l.col = 0
|
||||
}
|
||||
if l.next() == EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if l.pos > l.start {
|
||||
l.emit(TokenHTML)
|
||||
}
|
||||
|
||||
if l.inVerbatim {
|
||||
l.errorf("verbatim-tag not closed, got EOF.")
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) tokenize() {
|
||||
for state := l.stateCode; state != nil; {
|
||||
state = state()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) stateCode() lexerStateFn {
|
||||
outer_loop:
|
||||
for {
|
||||
switch {
|
||||
case l.accept(tokenSpaceChars):
|
||||
if l.value() == "\n" {
|
||||
return l.errorf("Newline not allowed within tag/variable.")
|
||||
}
|
||||
l.ignore()
|
||||
continue
|
||||
case l.accept(tokenIdentifierChars):
|
||||
return l.stateIdentifier
|
||||
case l.accept(tokenDigits):
|
||||
return l.stateNumber
|
||||
case l.accept(`"'`):
|
||||
return l.stateString
|
||||
}
|
||||
|
||||
// Check for symbol
|
||||
for _, sym := range TokenSymbols {
|
||||
if strings.HasPrefix(l.input[l.start:], sym) {
|
||||
l.pos += len(sym)
|
||||
l.col += l.length()
|
||||
l.emit(TokenSymbol)
|
||||
|
||||
if sym == "%}" || sym == "-%}" || sym == "}}" || sym == "-}}" {
|
||||
// Tag/variable end, return after emit
|
||||
return nil
|
||||
}
|
||||
|
||||
continue outer_loop
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// Normal shut down
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lexer) stateIdentifier() lexerStateFn {
|
||||
l.acceptRun(tokenIdentifierChars)
|
||||
l.acceptRun(tokenIdentifierCharsWithDigits)
|
||||
for _, kw := range TokenKeywords {
|
||||
if kw == l.value() {
|
||||
l.emit(TokenKeyword)
|
||||
return l.stateCode
|
||||
}
|
||||
}
|
||||
l.emit(TokenIdentifier)
|
||||
return l.stateCode
|
||||
}
|
||||
|
||||
func (l *lexer) stateNumber() lexerStateFn {
|
||||
l.acceptRun(tokenDigits)
|
||||
if l.accept(tokenIdentifierCharsWithDigits) {
|
||||
// This seems to be an identifier starting with a number.
|
||||
// See https://github.com/flosch/pongo2/issues/151
|
||||
return l.stateIdentifier()
|
||||
}
|
||||
/*
|
||||
Maybe context-sensitive number lexing?
|
||||
* comments.0.Text // first comment
|
||||
* usercomments.1.0 // second user, first comment
|
||||
* if (score >= 8.5) // 8.5 as a number
|
||||
|
||||
if l.peek() == '.' {
|
||||
l.accept(".")
|
||||
if !l.accept(tokenDigits) {
|
||||
return l.errorf("Malformed number.")
|
||||
}
|
||||
l.acceptRun(tokenDigits)
|
||||
}
|
||||
*/
|
||||
l.emit(TokenNumber)
|
||||
return l.stateCode
|
||||
}
|
||||
|
||||
func (l *lexer) stateString() lexerStateFn {
|
||||
quotationMark := l.value()
|
||||
l.ignore()
|
||||
l.startcol-- // we're starting the position at the first "
|
||||
for !l.accept(quotationMark) {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
// escape sequence
|
||||
switch l.peek() {
|
||||
case '"', '\\':
|
||||
l.next()
|
||||
default:
|
||||
return l.errorf("Unknown escape sequence: \\%c", l.peek())
|
||||
}
|
||||
case EOF:
|
||||
return l.errorf("Unexpected EOF, string not closed.")
|
||||
case '\n':
|
||||
return l.errorf("Newline in string is not allowed.")
|
||||
}
|
||||
}
|
||||
l.backup()
|
||||
l.emit(TokenString)
|
||||
|
||||
l.next()
|
||||
l.ignore()
|
||||
|
||||
return l.stateCode
|
||||
}
|
||||
16
vendor/github.com/flosch/pongo2/nodes.go
generated
vendored
Normal file
16
vendor/github.com/flosch/pongo2/nodes.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package pongo2
|
||||
|
||||
// The root document
|
||||
type nodeDocument struct {
|
||||
Nodes []INode
|
||||
}
|
||||
|
||||
func (doc *nodeDocument) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
for _, n := range doc.Nodes {
|
||||
err := n.Execute(ctx, writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
23
vendor/github.com/flosch/pongo2/nodes_html.go
generated
vendored
Normal file
23
vendor/github.com/flosch/pongo2/nodes_html.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type nodeHTML struct {
|
||||
token *Token
|
||||
trimLeft bool
|
||||
trimRight bool
|
||||
}
|
||||
|
||||
func (n *nodeHTML) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
res := n.token.Val
|
||||
if n.trimLeft {
|
||||
res = strings.TrimLeft(res, tokenSpaceChars)
|
||||
}
|
||||
if n.trimRight {
|
||||
res = strings.TrimRight(res, tokenSpaceChars)
|
||||
}
|
||||
writer.WriteString(res)
|
||||
return nil
|
||||
}
|
||||
16
vendor/github.com/flosch/pongo2/nodes_wrapper.go
generated
vendored
Normal file
16
vendor/github.com/flosch/pongo2/nodes_wrapper.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package pongo2
|
||||
|
||||
type NodeWrapper struct {
|
||||
Endtag string
|
||||
nodes []INode
|
||||
}
|
||||
|
||||
func (wrapper *NodeWrapper) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
for _, n := range wrapper.nodes {
|
||||
err := n.Execute(ctx, writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
26
vendor/github.com/flosch/pongo2/options.go
generated
vendored
Normal file
26
vendor/github.com/flosch/pongo2/options.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package pongo2
|
||||
|
||||
// Options allow you to change the behavior of template-engine.
|
||||
// You can change the options before calling the Execute method.
|
||||
type Options struct {
|
||||
// If this is set to true the first newline after a block is removed (block, not variable tag!). Defaults to false.
|
||||
TrimBlocks bool
|
||||
|
||||
// If this is set to true leading spaces and tabs are stripped from the start of a line to a block. Defaults to false
|
||||
LStripBlocks bool
|
||||
}
|
||||
|
||||
func newOptions() *Options {
|
||||
return &Options{
|
||||
TrimBlocks: false,
|
||||
LStripBlocks: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates this options from another options.
|
||||
func (opt *Options) Update(other *Options) *Options {
|
||||
opt.TrimBlocks = other.TrimBlocks
|
||||
opt.LStripBlocks = other.LStripBlocks
|
||||
|
||||
return opt
|
||||
}
|
||||
309
vendor/github.com/flosch/pongo2/parser.go
generated
vendored
Normal file
309
vendor/github.com/flosch/pongo2/parser.go
generated
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
type INode interface {
|
||||
Execute(*ExecutionContext, TemplateWriter) *Error
|
||||
}
|
||||
|
||||
type IEvaluator interface {
|
||||
INode
|
||||
GetPositionToken() *Token
|
||||
Evaluate(*ExecutionContext) (*Value, *Error)
|
||||
FilterApplied(name string) bool
|
||||
}
|
||||
|
||||
// The parser provides you a comprehensive and easy tool to
|
||||
// work with the template document and arguments provided by
|
||||
// the user for your custom tag.
|
||||
//
|
||||
// The parser works on a token list which will be provided by pongo2.
|
||||
// A token is a unit you can work with. Tokens are either of type identifier,
|
||||
// string, number, keyword, HTML or symbol.
|
||||
//
|
||||
// (See Token's documentation for more about tokens)
|
||||
type Parser struct {
|
||||
name string
|
||||
idx int
|
||||
tokens []*Token
|
||||
lastToken *Token
|
||||
|
||||
// if the parser parses a template document, here will be
|
||||
// a reference to it (needed to access the template through Tags)
|
||||
template *Template
|
||||
}
|
||||
|
||||
// Creates a new parser to parse tokens.
|
||||
// Used inside pongo2 to parse documents and to provide an easy-to-use
|
||||
// parser for tag authors
|
||||
func newParser(name string, tokens []*Token, template *Template) *Parser {
|
||||
p := &Parser{
|
||||
name: name,
|
||||
tokens: tokens,
|
||||
template: template,
|
||||
}
|
||||
if len(tokens) > 0 {
|
||||
p.lastToken = tokens[len(tokens)-1]
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Consume one token. It will be gone forever.
|
||||
func (p *Parser) Consume() {
|
||||
p.ConsumeN(1)
|
||||
}
|
||||
|
||||
// Consume N tokens. They will be gone forever.
|
||||
func (p *Parser) ConsumeN(count int) {
|
||||
p.idx += count
|
||||
}
|
||||
|
||||
// Returns the current token.
|
||||
func (p *Parser) Current() *Token {
|
||||
return p.Get(p.idx)
|
||||
}
|
||||
|
||||
// Returns the CURRENT token if the given type matches.
|
||||
// Consumes this token on success.
|
||||
func (p *Parser) MatchType(typ TokenType) *Token {
|
||||
if t := p.PeekType(typ); t != nil {
|
||||
p.Consume()
|
||||
return t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the CURRENT token if the given type AND value matches.
|
||||
// Consumes this token on success.
|
||||
func (p *Parser) Match(typ TokenType, val string) *Token {
|
||||
if t := p.Peek(typ, val); t != nil {
|
||||
p.Consume()
|
||||
return t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the CURRENT token if the given type AND *one* of
|
||||
// the given values matches.
|
||||
// Consumes this token on success.
|
||||
func (p *Parser) MatchOne(typ TokenType, vals ...string) *Token {
|
||||
for _, val := range vals {
|
||||
if t := p.Peek(typ, val); t != nil {
|
||||
p.Consume()
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the CURRENT token if the given type matches.
|
||||
// It DOES NOT consume the token.
|
||||
func (p *Parser) PeekType(typ TokenType) *Token {
|
||||
return p.PeekTypeN(0, typ)
|
||||
}
|
||||
|
||||
// Returns the CURRENT token if the given type AND value matches.
|
||||
// It DOES NOT consume the token.
|
||||
func (p *Parser) Peek(typ TokenType, val string) *Token {
|
||||
return p.PeekN(0, typ, val)
|
||||
}
|
||||
|
||||
// Returns the CURRENT token if the given type AND *one* of
|
||||
// the given values matches.
|
||||
// It DOES NOT consume the token.
|
||||
func (p *Parser) PeekOne(typ TokenType, vals ...string) *Token {
|
||||
for _, v := range vals {
|
||||
t := p.PeekN(0, typ, v)
|
||||
if t != nil {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the tokens[current position + shift] token if the
|
||||
// given type AND value matches for that token.
|
||||
// DOES NOT consume the token.
|
||||
func (p *Parser) PeekN(shift int, typ TokenType, val string) *Token {
|
||||
t := p.Get(p.idx + shift)
|
||||
if t != nil {
|
||||
if t.Typ == typ && t.Val == val {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the tokens[current position + shift] token if the given type matches.
|
||||
// DOES NOT consume the token for that token.
|
||||
func (p *Parser) PeekTypeN(shift int, typ TokenType) *Token {
|
||||
t := p.Get(p.idx + shift)
|
||||
if t != nil {
|
||||
if t.Typ == typ {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the UNCONSUMED token count.
|
||||
func (p *Parser) Remaining() int {
|
||||
return len(p.tokens) - p.idx
|
||||
}
|
||||
|
||||
// Returns the total token count.
|
||||
func (p *Parser) Count() int {
|
||||
return len(p.tokens)
|
||||
}
|
||||
|
||||
// Returns tokens[i] or NIL (if i >= len(tokens))
|
||||
func (p *Parser) Get(i int) *Token {
|
||||
if i < len(p.tokens) && i >= 0 {
|
||||
return p.tokens[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns tokens[current-position + shift] or NIL
|
||||
// (if (current-position + i) >= len(tokens))
|
||||
func (p *Parser) GetR(shift int) *Token {
|
||||
i := p.idx + shift
|
||||
return p.Get(i)
|
||||
}
|
||||
|
||||
// Error produces a nice error message and returns an error-object.
|
||||
// The 'token'-argument is optional. If provided, it will take
|
||||
// the token's position information. If not provided, it will
|
||||
// automatically use the CURRENT token's position information.
|
||||
func (p *Parser) Error(msg string, token *Token) *Error {
|
||||
if token == nil {
|
||||
// Set current token
|
||||
token = p.Current()
|
||||
if token == nil {
|
||||
// Set to last token
|
||||
if len(p.tokens) > 0 {
|
||||
token = p.tokens[len(p.tokens)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
var line, col int
|
||||
if token != nil {
|
||||
line = token.Line
|
||||
col = token.Col
|
||||
}
|
||||
return &Error{
|
||||
Template: p.template,
|
||||
Filename: p.name,
|
||||
Sender: "parser",
|
||||
Line: line,
|
||||
Column: col,
|
||||
Token: token,
|
||||
OrigError: errors.New(msg),
|
||||
}
|
||||
}
|
||||
|
||||
// Wraps all nodes between starting tag and "{% endtag %}" and provides
|
||||
// one simple interface to execute the wrapped nodes.
|
||||
// It returns a parser to process provided arguments to the tag.
|
||||
func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
|
||||
wrapper := &NodeWrapper{}
|
||||
|
||||
var tagArgs []*Token
|
||||
|
||||
for p.Remaining() > 0 {
|
||||
// New tag, check whether we have to stop wrapping here
|
||||
if p.Peek(TokenSymbol, "{%") != nil {
|
||||
tagIdent := p.PeekTypeN(1, TokenIdentifier)
|
||||
|
||||
if tagIdent != nil {
|
||||
// We've found a (!) end-tag
|
||||
|
||||
found := false
|
||||
for _, n := range names {
|
||||
if tagIdent.Val == n {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We only process the tag if we've found an end tag
|
||||
if found {
|
||||
// Okay, endtag found.
|
||||
p.ConsumeN(2) // '{%' tagname
|
||||
|
||||
for {
|
||||
if p.Match(TokenSymbol, "%}") != nil {
|
||||
// Okay, end the wrapping here
|
||||
wrapper.Endtag = tagIdent.Val
|
||||
return wrapper, newParser(p.template.name, tagArgs, p.template), nil
|
||||
}
|
||||
t := p.Current()
|
||||
p.Consume()
|
||||
if t == nil {
|
||||
return nil, nil, p.Error("Unexpected EOF.", p.lastToken)
|
||||
}
|
||||
tagArgs = append(tagArgs, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Otherwise process next element to be wrapped
|
||||
node, err := p.parseDocElement()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
wrapper.nodes = append(wrapper.nodes, node)
|
||||
}
|
||||
|
||||
return nil, nil, p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")),
|
||||
p.lastToken)
|
||||
}
|
||||
|
||||
// Skips all nodes between starting tag and "{% endtag %}"
|
||||
func (p *Parser) SkipUntilTag(names ...string) *Error {
|
||||
for p.Remaining() > 0 {
|
||||
// New tag, check whether we have to stop wrapping here
|
||||
if p.Peek(TokenSymbol, "{%") != nil {
|
||||
tagIdent := p.PeekTypeN(1, TokenIdentifier)
|
||||
|
||||
if tagIdent != nil {
|
||||
// We've found a (!) end-tag
|
||||
|
||||
found := false
|
||||
for _, n := range names {
|
||||
if tagIdent.Val == n {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We only process the tag if we've found an end tag
|
||||
if found {
|
||||
// Okay, endtag found.
|
||||
p.ConsumeN(2) // '{%' tagname
|
||||
|
||||
for {
|
||||
if p.Match(TokenSymbol, "%}") != nil {
|
||||
// Done skipping, exit.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
t := p.Current()
|
||||
p.Consume()
|
||||
if t == nil {
|
||||
return p.Error("Unexpected EOF.", p.lastToken)
|
||||
}
|
||||
}
|
||||
|
||||
return p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")), p.lastToken)
|
||||
}
|
||||
59
vendor/github.com/flosch/pongo2/parser_document.go
generated
vendored
Normal file
59
vendor/github.com/flosch/pongo2/parser_document.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package pongo2
|
||||
|
||||
// Doc = { ( Filter | Tag | HTML ) }
|
||||
func (p *Parser) parseDocElement() (INode, *Error) {
|
||||
t := p.Current()
|
||||
|
||||
switch t.Typ {
|
||||
case TokenHTML:
|
||||
n := &nodeHTML{token: t}
|
||||
left := p.PeekTypeN(-1, TokenSymbol)
|
||||
right := p.PeekTypeN(1, TokenSymbol)
|
||||
n.trimLeft = left != nil && left.TrimWhitespaces
|
||||
n.trimRight = right != nil && right.TrimWhitespaces
|
||||
p.Consume() // consume HTML element
|
||||
return n, nil
|
||||
case TokenSymbol:
|
||||
switch t.Val {
|
||||
case "{{":
|
||||
// parse variable
|
||||
variable, err := p.parseVariableElement()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return variable, nil
|
||||
case "{%":
|
||||
// parse tag
|
||||
tag, err := p.parseTagElement()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
return nil, p.Error("Unexpected token (only HTML/tags/filters in templates allowed)", t)
|
||||
}
|
||||
|
||||
func (tpl *Template) parse() *Error {
|
||||
tpl.parser = newParser(tpl.name, tpl.tokens, tpl)
|
||||
doc, err := tpl.parser.parseDocument()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tpl.root = doc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseDocument() (*nodeDocument, *Error) {
|
||||
doc := &nodeDocument{}
|
||||
|
||||
for p.Remaining() > 0 {
|
||||
node, err := p.parseDocElement()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
doc.Nodes = append(doc.Nodes, node)
|
||||
}
|
||||
|
||||
return doc, nil
|
||||
}
|
||||
503
vendor/github.com/flosch/pongo2/parser_expression.go
generated
vendored
Normal file
503
vendor/github.com/flosch/pongo2/parser_expression.go
generated
vendored
Normal file
@@ -0,0 +1,503 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Expression struct {
|
||||
// TODO: Add location token?
|
||||
expr1 IEvaluator
|
||||
expr2 IEvaluator
|
||||
opToken *Token
|
||||
}
|
||||
|
||||
type relationalExpression struct {
|
||||
// TODO: Add location token?
|
||||
expr1 IEvaluator
|
||||
expr2 IEvaluator
|
||||
opToken *Token
|
||||
}
|
||||
|
||||
type simpleExpression struct {
|
||||
negate bool
|
||||
negativeSign bool
|
||||
term1 IEvaluator
|
||||
term2 IEvaluator
|
||||
opToken *Token
|
||||
}
|
||||
|
||||
type term struct {
|
||||
// TODO: Add location token?
|
||||
factor1 IEvaluator
|
||||
factor2 IEvaluator
|
||||
opToken *Token
|
||||
}
|
||||
|
||||
type power struct {
|
||||
// TODO: Add location token?
|
||||
power1 IEvaluator
|
||||
power2 IEvaluator
|
||||
}
|
||||
|
||||
func (expr *Expression) FilterApplied(name string) bool {
|
||||
return expr.expr1.FilterApplied(name) && (expr.expr2 == nil ||
|
||||
(expr.expr2 != nil && expr.expr2.FilterApplied(name)))
|
||||
}
|
||||
|
||||
func (expr *relationalExpression) FilterApplied(name string) bool {
|
||||
return expr.expr1.FilterApplied(name) && (expr.expr2 == nil ||
|
||||
(expr.expr2 != nil && expr.expr2.FilterApplied(name)))
|
||||
}
|
||||
|
||||
func (expr *simpleExpression) FilterApplied(name string) bool {
|
||||
return expr.term1.FilterApplied(name) && (expr.term2 == nil ||
|
||||
(expr.term2 != nil && expr.term2.FilterApplied(name)))
|
||||
}
|
||||
|
||||
func (expr *term) FilterApplied(name string) bool {
|
||||
return expr.factor1.FilterApplied(name) && (expr.factor2 == nil ||
|
||||
(expr.factor2 != nil && expr.factor2.FilterApplied(name)))
|
||||
}
|
||||
|
||||
func (expr *power) FilterApplied(name string) bool {
|
||||
return expr.power1.FilterApplied(name) && (expr.power2 == nil ||
|
||||
(expr.power2 != nil && expr.power2.FilterApplied(name)))
|
||||
}
|
||||
|
||||
func (expr *Expression) GetPositionToken() *Token {
|
||||
return expr.expr1.GetPositionToken()
|
||||
}
|
||||
|
||||
func (expr *relationalExpression) GetPositionToken() *Token {
|
||||
return expr.expr1.GetPositionToken()
|
||||
}
|
||||
|
||||
func (expr *simpleExpression) GetPositionToken() *Token {
|
||||
return expr.term1.GetPositionToken()
|
||||
}
|
||||
|
||||
func (expr *term) GetPositionToken() *Token {
|
||||
return expr.factor1.GetPositionToken()
|
||||
}
|
||||
|
||||
func (expr *power) GetPositionToken() *Token {
|
||||
return expr.power1.GetPositionToken()
|
||||
}
|
||||
|
||||
func (expr *Expression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
value, err := expr.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writer.WriteString(value.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (expr *relationalExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
value, err := expr.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writer.WriteString(value.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (expr *simpleExpression) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
value, err := expr.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writer.WriteString(value.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (expr *term) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
value, err := expr.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writer.WriteString(value.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (expr *power) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
value, err := expr.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writer.WriteString(value.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (expr *Expression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||
v1, err := expr.expr1.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expr.expr2 != nil {
|
||||
switch expr.opToken.Val {
|
||||
case "and", "&&":
|
||||
if !v1.IsTrue() {
|
||||
return AsValue(false), nil
|
||||
} else {
|
||||
v2, err := expr.expr2.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return AsValue(v2.IsTrue()), nil
|
||||
}
|
||||
case "or", "||":
|
||||
if v1.IsTrue() {
|
||||
return AsValue(true), nil
|
||||
} else {
|
||||
v2, err := expr.expr2.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return AsValue(v2.IsTrue()), nil
|
||||
}
|
||||
default:
|
||||
return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken)
|
||||
}
|
||||
} else {
|
||||
return v1, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (expr *relationalExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||
v1, err := expr.expr1.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expr.expr2 != nil {
|
||||
v2, err := expr.expr2.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch expr.opToken.Val {
|
||||
case "<=":
|
||||
if v1.IsFloat() || v2.IsFloat() {
|
||||
return AsValue(v1.Float() <= v2.Float()), nil
|
||||
}
|
||||
return AsValue(v1.Integer() <= v2.Integer()), nil
|
||||
case ">=":
|
||||
if v1.IsFloat() || v2.IsFloat() {
|
||||
return AsValue(v1.Float() >= v2.Float()), nil
|
||||
}
|
||||
return AsValue(v1.Integer() >= v2.Integer()), nil
|
||||
case "==":
|
||||
return AsValue(v1.EqualValueTo(v2)), nil
|
||||
case ">":
|
||||
if v1.IsFloat() || v2.IsFloat() {
|
||||
return AsValue(v1.Float() > v2.Float()), nil
|
||||
}
|
||||
return AsValue(v1.Integer() > v2.Integer()), nil
|
||||
case "<":
|
||||
if v1.IsFloat() || v2.IsFloat() {
|
||||
return AsValue(v1.Float() < v2.Float()), nil
|
||||
}
|
||||
return AsValue(v1.Integer() < v2.Integer()), nil
|
||||
case "!=", "<>":
|
||||
return AsValue(!v1.EqualValueTo(v2)), nil
|
||||
case "in":
|
||||
return AsValue(v2.Contains(v1)), nil
|
||||
default:
|
||||
return nil, ctx.Error(fmt.Sprintf("unimplemented: %s", expr.opToken.Val), expr.opToken)
|
||||
}
|
||||
} else {
|
||||
return v1, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (expr *simpleExpression) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||
t1, err := expr.term1.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := t1
|
||||
|
||||
if expr.negate {
|
||||
result = result.Negate()
|
||||
}
|
||||
|
||||
if expr.negativeSign {
|
||||
if result.IsNumber() {
|
||||
switch {
|
||||
case result.IsFloat():
|
||||
result = AsValue(-1 * result.Float())
|
||||
case result.IsInteger():
|
||||
result = AsValue(-1 * result.Integer())
|
||||
default:
|
||||
return nil, ctx.Error("Operation between a number and a non-(float/integer) is not possible", nil)
|
||||
}
|
||||
} else {
|
||||
return nil, ctx.Error("Negative sign on a non-number expression", expr.GetPositionToken())
|
||||
}
|
||||
}
|
||||
|
||||
if expr.term2 != nil {
|
||||
t2, err := expr.term2.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch expr.opToken.Val {
|
||||
case "+":
|
||||
if result.IsFloat() || t2.IsFloat() {
|
||||
// Result will be a float
|
||||
return AsValue(result.Float() + t2.Float()), nil
|
||||
}
|
||||
// Result will be an integer
|
||||
return AsValue(result.Integer() + t2.Integer()), nil
|
||||
case "-":
|
||||
if result.IsFloat() || t2.IsFloat() {
|
||||
// Result will be a float
|
||||
return AsValue(result.Float() - t2.Float()), nil
|
||||
}
|
||||
// Result will be an integer
|
||||
return AsValue(result.Integer() - t2.Integer()), nil
|
||||
default:
|
||||
return nil, ctx.Error("Unimplemented", expr.GetPositionToken())
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (expr *term) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||
f1, err := expr.factor1.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expr.factor2 != nil {
|
||||
f2, err := expr.factor2.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch expr.opToken.Val {
|
||||
case "*":
|
||||
if f1.IsFloat() || f2.IsFloat() {
|
||||
// Result will be float
|
||||
return AsValue(f1.Float() * f2.Float()), nil
|
||||
}
|
||||
// Result will be int
|
||||
return AsValue(f1.Integer() * f2.Integer()), nil
|
||||
case "/":
|
||||
if f1.IsFloat() || f2.IsFloat() {
|
||||
// Result will be float
|
||||
return AsValue(f1.Float() / f2.Float()), nil
|
||||
}
|
||||
// Result will be int
|
||||
return AsValue(f1.Integer() / f2.Integer()), nil
|
||||
case "%":
|
||||
// Result will be int
|
||||
return AsValue(f1.Integer() % f2.Integer()), nil
|
||||
default:
|
||||
return nil, ctx.Error("unimplemented", expr.opToken)
|
||||
}
|
||||
} else {
|
||||
return f1, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (expr *power) Evaluate(ctx *ExecutionContext) (*Value, *Error) {
|
||||
p1, err := expr.power1.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expr.power2 != nil {
|
||||
p2, err := expr.power2.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return AsValue(math.Pow(p1.Float(), p2.Float())), nil
|
||||
}
|
||||
return p1, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseFactor() (IEvaluator, *Error) {
|
||||
if p.Match(TokenSymbol, "(") != nil {
|
||||
expr, err := p.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.Match(TokenSymbol, ")") == nil {
|
||||
return nil, p.Error("Closing bracket expected after expression", nil)
|
||||
}
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
return p.parseVariableOrLiteralWithFilter()
|
||||
}
|
||||
|
||||
func (p *Parser) parsePower() (IEvaluator, *Error) {
|
||||
pw := new(power)
|
||||
|
||||
power1, err := p.parseFactor()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pw.power1 = power1
|
||||
|
||||
if p.Match(TokenSymbol, "^") != nil {
|
||||
power2, err := p.parsePower()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pw.power2 = power2
|
||||
}
|
||||
|
||||
if pw.power2 == nil {
|
||||
// Shortcut for faster evaluation
|
||||
return pw.power1, nil
|
||||
}
|
||||
|
||||
return pw, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseTerm() (IEvaluator, *Error) {
|
||||
returnTerm := new(term)
|
||||
|
||||
factor1, err := p.parsePower()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
returnTerm.factor1 = factor1
|
||||
|
||||
for p.PeekOne(TokenSymbol, "*", "/", "%") != nil {
|
||||
if returnTerm.opToken != nil {
|
||||
// Create new sub-term
|
||||
returnTerm = &term{
|
||||
factor1: returnTerm,
|
||||
}
|
||||
}
|
||||
|
||||
op := p.Current()
|
||||
p.Consume()
|
||||
|
||||
factor2, err := p.parsePower()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
returnTerm.opToken = op
|
||||
returnTerm.factor2 = factor2
|
||||
}
|
||||
|
||||
if returnTerm.opToken == nil {
|
||||
// Shortcut for faster evaluation
|
||||
return returnTerm.factor1, nil
|
||||
}
|
||||
|
||||
return returnTerm, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseSimpleExpression() (IEvaluator, *Error) {
|
||||
expr := new(simpleExpression)
|
||||
|
||||
if sign := p.MatchOne(TokenSymbol, "+", "-"); sign != nil {
|
||||
if sign.Val == "-" {
|
||||
expr.negativeSign = true
|
||||
}
|
||||
}
|
||||
|
||||
if p.Match(TokenSymbol, "!") != nil || p.Match(TokenKeyword, "not") != nil {
|
||||
expr.negate = true
|
||||
}
|
||||
|
||||
term1, err := p.parseTerm()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.term1 = term1
|
||||
|
||||
for p.PeekOne(TokenSymbol, "+", "-") != nil {
|
||||
if expr.opToken != nil {
|
||||
// New sub expr
|
||||
expr = &simpleExpression{
|
||||
term1: expr,
|
||||
}
|
||||
}
|
||||
|
||||
op := p.Current()
|
||||
p.Consume()
|
||||
|
||||
term2, err := p.parseTerm()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expr.term2 = term2
|
||||
expr.opToken = op
|
||||
}
|
||||
|
||||
if expr.negate == false && expr.negativeSign == false && expr.term2 == nil {
|
||||
// Shortcut for faster evaluation
|
||||
return expr.term1, nil
|
||||
}
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseRelationalExpression() (IEvaluator, *Error) {
|
||||
expr1, err := p.parseSimpleExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
expr := &relationalExpression{
|
||||
expr1: expr1,
|
||||
}
|
||||
|
||||
if t := p.MatchOne(TokenSymbol, "==", "<=", ">=", "!=", "<>", ">", "<"); t != nil {
|
||||
expr2, err := p.parseRelationalExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.opToken = t
|
||||
expr.expr2 = expr2
|
||||
} else if t := p.MatchOne(TokenKeyword, "in"); t != nil {
|
||||
expr2, err := p.parseSimpleExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expr.opToken = t
|
||||
expr.expr2 = expr2
|
||||
}
|
||||
|
||||
if expr.expr2 == nil {
|
||||
// Shortcut for faster evaluation
|
||||
return expr.expr1, nil
|
||||
}
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
func (p *Parser) ParseExpression() (IEvaluator, *Error) {
|
||||
rexpr1, err := p.parseRelationalExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exp := &Expression{
|
||||
expr1: rexpr1,
|
||||
}
|
||||
|
||||
if p.PeekOne(TokenSymbol, "&&", "||") != nil || p.PeekOne(TokenKeyword, "and", "or") != nil {
|
||||
op := p.Current()
|
||||
p.Consume()
|
||||
expr2, err := p.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exp.expr2 = expr2
|
||||
exp.opToken = op
|
||||
}
|
||||
|
||||
if exp.expr2 == nil {
|
||||
// Shortcut for faster evaluation
|
||||
return exp.expr1, nil
|
||||
}
|
||||
|
||||
return exp, nil
|
||||
}
|
||||
14
vendor/github.com/flosch/pongo2/pongo2.go
generated
vendored
Normal file
14
vendor/github.com/flosch/pongo2/pongo2.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package pongo2
|
||||
|
||||
// Version string
|
||||
const Version = "dev"
|
||||
|
||||
// Must panics, if a Template couldn't successfully parsed. This is how you
|
||||
// would use it:
|
||||
// var baseTemplate = pongo2.Must(pongo2.FromFile("templates/base.html"))
|
||||
func Must(tpl *Template, err error) *Template {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tpl
|
||||
}
|
||||
135
vendor/github.com/flosch/pongo2/tags.go
generated
vendored
Normal file
135
vendor/github.com/flosch/pongo2/tags.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
package pongo2
|
||||
|
||||
/* Incomplete:
|
||||
-----------
|
||||
|
||||
verbatim (only the "name" argument is missing for verbatim)
|
||||
|
||||
Reconsideration:
|
||||
----------------
|
||||
|
||||
debug (reason: not sure what to output yet)
|
||||
regroup / Grouping on other properties (reason: maybe too python-specific; not sure how useful this would be in Go)
|
||||
|
||||
Following built-in tags wont be added:
|
||||
--------------------------------------
|
||||
|
||||
csrf_token (reason: web-framework specific)
|
||||
load (reason: python-specific)
|
||||
url (reason: web-framework specific)
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
type INodeTag interface {
|
||||
INode
|
||||
}
|
||||
|
||||
// This is the function signature of the tag's parser you will have
|
||||
// to implement in order to create a new tag.
|
||||
//
|
||||
// 'doc' is providing access to the whole document while 'arguments'
|
||||
// is providing access to the user's arguments to the tag:
|
||||
//
|
||||
// {% your_tag_name some "arguments" 123 %}
|
||||
//
|
||||
// start_token will be the *Token with the tag's name in it (here: your_tag_name).
|
||||
//
|
||||
// Please see the Parser documentation on how to use the parser.
|
||||
// See RegisterTag()'s documentation for more information about
|
||||
// writing a tag as well.
|
||||
type TagParser func(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error)
|
||||
|
||||
type tag struct {
|
||||
name string
|
||||
parser TagParser
|
||||
}
|
||||
|
||||
var tags map[string]*tag
|
||||
|
||||
func init() {
|
||||
tags = make(map[string]*tag)
|
||||
}
|
||||
|
||||
// Registers a new tag. You usually want to call this
|
||||
// function in the tag's init() function:
|
||||
// http://golang.org/doc/effective_go.html#init
|
||||
//
|
||||
// See http://www.florian-schlachter.de/post/pongo2/ for more about
|
||||
// writing filters and tags.
|
||||
func RegisterTag(name string, parserFn TagParser) error {
|
||||
_, existing := tags[name]
|
||||
if existing {
|
||||
return errors.Errorf("tag with name '%s' is already registered", name)
|
||||
}
|
||||
tags[name] = &tag{
|
||||
name: name,
|
||||
parser: parserFn,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replaces an already registered tag with a new implementation. Use this
|
||||
// function with caution since it allows you to change existing tag behaviour.
|
||||
func ReplaceTag(name string, parserFn TagParser) error {
|
||||
_, existing := tags[name]
|
||||
if !existing {
|
||||
return errors.Errorf("tag with name '%s' does not exist (therefore cannot be overridden)", name)
|
||||
}
|
||||
tags[name] = &tag{
|
||||
name: name,
|
||||
parser: parserFn,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tag = "{%" IDENT ARGS "%}"
|
||||
func (p *Parser) parseTagElement() (INodeTag, *Error) {
|
||||
p.Consume() // consume "{%"
|
||||
tokenName := p.MatchType(TokenIdentifier)
|
||||
|
||||
// Check for identifier
|
||||
if tokenName == nil {
|
||||
return nil, p.Error("Tag name must be an identifier.", nil)
|
||||
}
|
||||
|
||||
// Check for the existing tag
|
||||
tag, exists := tags[tokenName.Val]
|
||||
if !exists {
|
||||
// Does not exists
|
||||
return nil, p.Error(fmt.Sprintf("Tag '%s' not found (or beginning tag not provided)", tokenName.Val), tokenName)
|
||||
}
|
||||
|
||||
// Check sandbox tag restriction
|
||||
if _, isBanned := p.template.set.bannedTags[tokenName.Val]; isBanned {
|
||||
return nil, p.Error(fmt.Sprintf("Usage of tag '%s' is not allowed (sandbox restriction active).", tokenName.Val), tokenName)
|
||||
}
|
||||
|
||||
var argsToken []*Token
|
||||
for p.Peek(TokenSymbol, "%}") == nil && p.Remaining() > 0 {
|
||||
// Add token to args
|
||||
argsToken = append(argsToken, p.Current())
|
||||
p.Consume() // next token
|
||||
}
|
||||
|
||||
// EOF?
|
||||
if p.Remaining() == 0 {
|
||||
return nil, p.Error("Unexpectedly reached EOF, no tag end found.", p.lastToken)
|
||||
}
|
||||
|
||||
p.Match(TokenSymbol, "%}")
|
||||
|
||||
argParser := newParser(p.name, argsToken, p.template)
|
||||
if len(argsToken) == 0 {
|
||||
// This is done to have nice EOF error messages
|
||||
argParser.lastToken = tokenName
|
||||
}
|
||||
|
||||
p.template.level++
|
||||
defer func() { p.template.level-- }()
|
||||
return tag.parser(p, tokenName, argParser)
|
||||
}
|
||||
52
vendor/github.com/flosch/pongo2/tags_autoescape.go
generated
vendored
Normal file
52
vendor/github.com/flosch/pongo2/tags_autoescape.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package pongo2
|
||||
|
||||
type tagAutoescapeNode struct {
|
||||
wrapper *NodeWrapper
|
||||
autoescape bool
|
||||
}
|
||||
|
||||
func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
old := ctx.Autoescape
|
||||
ctx.Autoescape = node.autoescape
|
||||
|
||||
err := node.wrapper.Execute(ctx, writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Autoescape = old
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
autoescapeNode := &tagAutoescapeNode{}
|
||||
|
||||
wrapper, _, err := doc.WrapUntilTag("endautoescape")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
autoescapeNode.wrapper = wrapper
|
||||
|
||||
modeToken := arguments.MatchType(TokenIdentifier)
|
||||
if modeToken == nil {
|
||||
return nil, arguments.Error("A mode is required for autoescape-tag.", nil)
|
||||
}
|
||||
if modeToken.Val == "on" {
|
||||
autoescapeNode.autoescape = true
|
||||
} else if modeToken.Val == "off" {
|
||||
autoescapeNode.autoescape = false
|
||||
} else {
|
||||
return nil, arguments.Error("Only 'on' or 'off' is valid as an autoescape-mode.", nil)
|
||||
}
|
||||
|
||||
if arguments.Remaining() > 0 {
|
||||
return nil, arguments.Error("Malformed autoescape-tag arguments.", nil)
|
||||
}
|
||||
|
||||
return autoescapeNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("autoescape", tagAutoescapeParser)
|
||||
}
|
||||
129
vendor/github.com/flosch/pongo2/tags_block.go
generated
vendored
Normal file
129
vendor/github.com/flosch/pongo2/tags_block.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type tagBlockNode struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (node *tagBlockNode) getBlockWrappers(tpl *Template) []*NodeWrapper {
|
||||
nodeWrappers := make([]*NodeWrapper, 0)
|
||||
var t *NodeWrapper
|
||||
|
||||
for tpl != nil {
|
||||
t = tpl.blocks[node.name]
|
||||
if t != nil {
|
||||
nodeWrappers = append(nodeWrappers, t)
|
||||
}
|
||||
tpl = tpl.child
|
||||
}
|
||||
|
||||
return nodeWrappers
|
||||
}
|
||||
|
||||
func (node *tagBlockNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
tpl := ctx.template
|
||||
if tpl == nil {
|
||||
panic("internal error: tpl == nil")
|
||||
}
|
||||
|
||||
// Determine the block to execute
|
||||
blockWrappers := node.getBlockWrappers(tpl)
|
||||
lenBlockWrappers := len(blockWrappers)
|
||||
|
||||
if lenBlockWrappers == 0 {
|
||||
return ctx.Error("internal error: len(block_wrappers) == 0 in tagBlockNode.Execute()", nil)
|
||||
}
|
||||
|
||||
blockWrapper := blockWrappers[lenBlockWrappers-1]
|
||||
ctx.Private["block"] = tagBlockInformation{
|
||||
ctx: ctx,
|
||||
wrappers: blockWrappers[0 : lenBlockWrappers-1],
|
||||
}
|
||||
err := blockWrapper.Execute(ctx, writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type tagBlockInformation struct {
|
||||
ctx *ExecutionContext
|
||||
wrappers []*NodeWrapper
|
||||
}
|
||||
|
||||
func (t tagBlockInformation) Super() string {
|
||||
lenWrappers := len(t.wrappers)
|
||||
|
||||
if lenWrappers == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
superCtx := NewChildExecutionContext(t.ctx)
|
||||
superCtx.Private["block"] = tagBlockInformation{
|
||||
ctx: t.ctx,
|
||||
wrappers: t.wrappers[0 : lenWrappers-1],
|
||||
}
|
||||
|
||||
blockWrapper := t.wrappers[lenWrappers-1]
|
||||
buf := bytes.NewBufferString("")
|
||||
err := blockWrapper.Execute(superCtx, &templateWriter{buf})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
if arguments.Count() == 0 {
|
||||
return nil, arguments.Error("Tag 'block' requires an identifier.", nil)
|
||||
}
|
||||
|
||||
nameToken := arguments.MatchType(TokenIdentifier)
|
||||
if nameToken == nil {
|
||||
return nil, arguments.Error("First argument for tag 'block' must be an identifier.", nil)
|
||||
}
|
||||
|
||||
if arguments.Remaining() != 0 {
|
||||
return nil, arguments.Error("Tag 'block' takes exactly 1 argument (an identifier).", nil)
|
||||
}
|
||||
|
||||
wrapper, endtagargs, err := doc.WrapUntilTag("endblock")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if endtagargs.Remaining() > 0 {
|
||||
endtagnameToken := endtagargs.MatchType(TokenIdentifier)
|
||||
if endtagnameToken != nil {
|
||||
if endtagnameToken.Val != nameToken.Val {
|
||||
return nil, endtagargs.Error(fmt.Sprintf("Name for 'endblock' must equal to 'block'-tag's name ('%s' != '%s').",
|
||||
nameToken.Val, endtagnameToken.Val), nil)
|
||||
}
|
||||
}
|
||||
|
||||
if endtagnameToken == nil || endtagargs.Remaining() > 0 {
|
||||
return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endblock'.", nil)
|
||||
}
|
||||
}
|
||||
|
||||
tpl := doc.template
|
||||
if tpl == nil {
|
||||
panic("internal error: tpl == nil")
|
||||
}
|
||||
_, hasBlock := tpl.blocks[nameToken.Val]
|
||||
if !hasBlock {
|
||||
tpl.blocks[nameToken.Val] = wrapper
|
||||
} else {
|
||||
return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", nameToken.Val), nil)
|
||||
}
|
||||
|
||||
return &tagBlockNode{name: nameToken.Val}, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("block", tagBlockParser)
|
||||
}
|
||||
27
vendor/github.com/flosch/pongo2/tags_comment.go
generated
vendored
Normal file
27
vendor/github.com/flosch/pongo2/tags_comment.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package pongo2
|
||||
|
||||
type tagCommentNode struct{}
|
||||
|
||||
func (node *tagCommentNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
commentNode := &tagCommentNode{}
|
||||
|
||||
// TODO: Process the endtag's arguments (see django 'comment'-tag documentation)
|
||||
err := doc.SkipUntilTag("endcomment")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if arguments.Count() != 0 {
|
||||
return nil, arguments.Error("Tag 'comment' does not take any argument.", nil)
|
||||
}
|
||||
|
||||
return commentNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("comment", tagCommentParser)
|
||||
}
|
||||
106
vendor/github.com/flosch/pongo2/tags_cycle.go
generated
vendored
Normal file
106
vendor/github.com/flosch/pongo2/tags_cycle.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package pongo2
|
||||
|
||||
type tagCycleValue struct {
|
||||
node *tagCycleNode
|
||||
value *Value
|
||||
}
|
||||
|
||||
type tagCycleNode struct {
|
||||
position *Token
|
||||
args []IEvaluator
|
||||
idx int
|
||||
asName string
|
||||
silent bool
|
||||
}
|
||||
|
||||
func (cv *tagCycleValue) String() string {
|
||||
return cv.value.String()
|
||||
}
|
||||
|
||||
func (node *tagCycleNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
item := node.args[node.idx%len(node.args)]
|
||||
node.idx++
|
||||
|
||||
val, err := item.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if t, ok := val.Interface().(*tagCycleValue); ok {
|
||||
// {% cycle "test1" "test2"
|
||||
// {% cycle cycleitem %}
|
||||
|
||||
// Update the cycle value with next value
|
||||
item := t.node.args[t.node.idx%len(t.node.args)]
|
||||
t.node.idx++
|
||||
|
||||
val, err := item.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.value = val
|
||||
|
||||
if !t.node.silent {
|
||||
writer.WriteString(val.String())
|
||||
}
|
||||
} else {
|
||||
// Regular call
|
||||
|
||||
cycleValue := &tagCycleValue{
|
||||
node: node,
|
||||
value: val,
|
||||
}
|
||||
|
||||
if node.asName != "" {
|
||||
ctx.Private[node.asName] = cycleValue
|
||||
}
|
||||
if !node.silent {
|
||||
writer.WriteString(val.String())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HINT: We're not supporting the old comma-separated list of expressions argument-style
|
||||
func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
cycleNode := &tagCycleNode{
|
||||
position: start,
|
||||
}
|
||||
|
||||
for arguments.Remaining() > 0 {
|
||||
node, err := arguments.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cycleNode.args = append(cycleNode.args, node)
|
||||
|
||||
if arguments.MatchOne(TokenKeyword, "as") != nil {
|
||||
// as
|
||||
|
||||
nameToken := arguments.MatchType(TokenIdentifier)
|
||||
if nameToken == nil {
|
||||
return nil, arguments.Error("Name (identifier) expected after 'as'.", nil)
|
||||
}
|
||||
cycleNode.asName = nameToken.Val
|
||||
|
||||
if arguments.MatchOne(TokenIdentifier, "silent") != nil {
|
||||
cycleNode.silent = true
|
||||
}
|
||||
|
||||
// Now we're finished
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if arguments.Remaining() > 0 {
|
||||
return nil, arguments.Error("Malformed cycle-tag.", nil)
|
||||
}
|
||||
|
||||
return cycleNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("cycle", tagCycleParser)
|
||||
}
|
||||
52
vendor/github.com/flosch/pongo2/tags_extends.go
generated
vendored
Normal file
52
vendor/github.com/flosch/pongo2/tags_extends.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package pongo2
|
||||
|
||||
type tagExtendsNode struct {
|
||||
filename string
|
||||
}
|
||||
|
||||
func (node *tagExtendsNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
extendsNode := &tagExtendsNode{}
|
||||
|
||||
if doc.template.level > 1 {
|
||||
return nil, arguments.Error("The 'extends' tag can only defined on root level.", start)
|
||||
}
|
||||
|
||||
if doc.template.parent != nil {
|
||||
// Already one parent
|
||||
return nil, arguments.Error("This template has already one parent.", start)
|
||||
}
|
||||
|
||||
if filenameToken := arguments.MatchType(TokenString); filenameToken != nil {
|
||||
// prepared, static template
|
||||
|
||||
// Get parent's filename
|
||||
parentFilename := doc.template.set.resolveFilename(doc.template, filenameToken.Val)
|
||||
|
||||
// Parse the parent
|
||||
parentTemplate, err := doc.template.set.FromFile(parentFilename)
|
||||
if err != nil {
|
||||
return nil, err.(*Error)
|
||||
}
|
||||
|
||||
// Keep track of things
|
||||
parentTemplate.child = doc.template
|
||||
doc.template.parent = parentTemplate
|
||||
extendsNode.filename = parentFilename
|
||||
} else {
|
||||
return nil, arguments.Error("Tag 'extends' requires a template filename as string.", nil)
|
||||
}
|
||||
|
||||
if arguments.Remaining() > 0 {
|
||||
return nil, arguments.Error("Tag 'extends' does only take 1 argument.", nil)
|
||||
}
|
||||
|
||||
return extendsNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("extends", tagExtendsParser)
|
||||
}
|
||||
95
vendor/github.com/flosch/pongo2/tags_filter.go
generated
vendored
Normal file
95
vendor/github.com/flosch/pongo2/tags_filter.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type nodeFilterCall struct {
|
||||
name string
|
||||
paramExpr IEvaluator
|
||||
}
|
||||
|
||||
type tagFilterNode struct {
|
||||
position *Token
|
||||
bodyWrapper *NodeWrapper
|
||||
filterChain []*nodeFilterCall
|
||||
}
|
||||
|
||||
func (node *tagFilterNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
temp := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB size
|
||||
|
||||
err := node.bodyWrapper.Execute(ctx, temp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value := AsValue(temp.String())
|
||||
|
||||
for _, call := range node.filterChain {
|
||||
var param *Value
|
||||
if call.paramExpr != nil {
|
||||
param, err = call.paramExpr.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
param = AsValue(nil)
|
||||
}
|
||||
value, err = ApplyFilter(call.name, value, param)
|
||||
if err != nil {
|
||||
return ctx.Error(err.Error(), node.position)
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteString(value.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
filterNode := &tagFilterNode{
|
||||
position: start,
|
||||
}
|
||||
|
||||
wrapper, _, err := doc.WrapUntilTag("endfilter")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filterNode.bodyWrapper = wrapper
|
||||
|
||||
for arguments.Remaining() > 0 {
|
||||
filterCall := &nodeFilterCall{}
|
||||
|
||||
nameToken := arguments.MatchType(TokenIdentifier)
|
||||
if nameToken == nil {
|
||||
return nil, arguments.Error("Expected a filter name (identifier).", nil)
|
||||
}
|
||||
filterCall.name = nameToken.Val
|
||||
|
||||
if arguments.MatchOne(TokenSymbol, ":") != nil {
|
||||
// Filter parameter
|
||||
// NOTICE: we can't use ParseExpression() here, because it would parse the next filter "|..." as well in the argument list
|
||||
expr, err := arguments.parseVariableOrLiteral()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filterCall.paramExpr = expr
|
||||
}
|
||||
|
||||
filterNode.filterChain = append(filterNode.filterChain, filterCall)
|
||||
|
||||
if arguments.MatchOne(TokenSymbol, "|") == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if arguments.Remaining() > 0 {
|
||||
return nil, arguments.Error("Malformed filter-tag arguments.", nil)
|
||||
}
|
||||
|
||||
return filterNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("filter", tagFilterParser)
|
||||
}
|
||||
49
vendor/github.com/flosch/pongo2/tags_firstof.go
generated
vendored
Normal file
49
vendor/github.com/flosch/pongo2/tags_firstof.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package pongo2
|
||||
|
||||
type tagFirstofNode struct {
|
||||
position *Token
|
||||
args []IEvaluator
|
||||
}
|
||||
|
||||
func (node *tagFirstofNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
for _, arg := range node.args {
|
||||
val, err := arg.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if val.IsTrue() {
|
||||
if ctx.Autoescape && !arg.FilterApplied("safe") {
|
||||
val, err = ApplyFilter("escape", val, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteString(val.String())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
firstofNode := &tagFirstofNode{
|
||||
position: start,
|
||||
}
|
||||
|
||||
for arguments.Remaining() > 0 {
|
||||
node, err := arguments.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
firstofNode.args = append(firstofNode.args, node)
|
||||
}
|
||||
|
||||
return firstofNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("firstof", tagFirstofParser)
|
||||
}
|
||||
159
vendor/github.com/flosch/pongo2/tags_for.go
generated
vendored
Normal file
159
vendor/github.com/flosch/pongo2/tags_for.go
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
package pongo2
|
||||
|
||||
type tagForNode struct {
|
||||
key string
|
||||
value string // only for maps: for key, value in map
|
||||
objectEvaluator IEvaluator
|
||||
reversed bool
|
||||
sorted bool
|
||||
|
||||
bodyWrapper *NodeWrapper
|
||||
emptyWrapper *NodeWrapper
|
||||
}
|
||||
|
||||
type tagForLoopInformation struct {
|
||||
Counter int
|
||||
Counter0 int
|
||||
Revcounter int
|
||||
Revcounter0 int
|
||||
First bool
|
||||
Last bool
|
||||
Parentloop *tagForLoopInformation
|
||||
}
|
||||
|
||||
func (node *tagForNode) Execute(ctx *ExecutionContext, writer TemplateWriter) (forError *Error) {
|
||||
// Backup forloop (as parentloop in public context), key-name and value-name
|
||||
forCtx := NewChildExecutionContext(ctx)
|
||||
parentloop := forCtx.Private["forloop"]
|
||||
|
||||
// Create loop struct
|
||||
loopInfo := &tagForLoopInformation{
|
||||
First: true,
|
||||
}
|
||||
|
||||
// Is it a loop in a loop?
|
||||
if parentloop != nil {
|
||||
loopInfo.Parentloop = parentloop.(*tagForLoopInformation)
|
||||
}
|
||||
|
||||
// Register loopInfo in public context
|
||||
forCtx.Private["forloop"] = loopInfo
|
||||
|
||||
obj, err := node.objectEvaluator.Evaluate(forCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj.IterateOrder(func(idx, count int, key, value *Value) bool {
|
||||
// There's something to iterate over (correct type and at least 1 item)
|
||||
|
||||
// Update loop infos and public context
|
||||
forCtx.Private[node.key] = key
|
||||
if value != nil {
|
||||
forCtx.Private[node.value] = value
|
||||
}
|
||||
loopInfo.Counter = idx + 1
|
||||
loopInfo.Counter0 = idx
|
||||
if idx == 1 {
|
||||
loopInfo.First = false
|
||||
}
|
||||
if idx+1 == count {
|
||||
loopInfo.Last = true
|
||||
}
|
||||
loopInfo.Revcounter = count - idx // TODO: Not sure about this, have to look it up
|
||||
loopInfo.Revcounter0 = count - (idx + 1) // TODO: Not sure about this, have to look it up
|
||||
|
||||
// Render elements with updated context
|
||||
err := node.bodyWrapper.Execute(forCtx, writer)
|
||||
if err != nil {
|
||||
forError = err
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}, func() {
|
||||
// Nothing to iterate over (maybe wrong type or no items)
|
||||
if node.emptyWrapper != nil {
|
||||
err := node.emptyWrapper.Execute(forCtx, writer)
|
||||
if err != nil {
|
||||
forError = err
|
||||
}
|
||||
}
|
||||
}, node.reversed, node.sorted)
|
||||
|
||||
return forError
|
||||
}
|
||||
|
||||
func tagForParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
forNode := &tagForNode{}
|
||||
|
||||
// Arguments parsing
|
||||
var valueToken *Token
|
||||
keyToken := arguments.MatchType(TokenIdentifier)
|
||||
if keyToken == nil {
|
||||
return nil, arguments.Error("Expected an key identifier as first argument for 'for'-tag", nil)
|
||||
}
|
||||
|
||||
if arguments.Match(TokenSymbol, ",") != nil {
|
||||
// Value name is provided
|
||||
valueToken = arguments.MatchType(TokenIdentifier)
|
||||
if valueToken == nil {
|
||||
return nil, arguments.Error("Value name must be an identifier.", nil)
|
||||
}
|
||||
}
|
||||
|
||||
if arguments.Match(TokenKeyword, "in") == nil {
|
||||
return nil, arguments.Error("Expected keyword 'in'.", nil)
|
||||
}
|
||||
|
||||
objectEvaluator, err := arguments.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
forNode.objectEvaluator = objectEvaluator
|
||||
forNode.key = keyToken.Val
|
||||
if valueToken != nil {
|
||||
forNode.value = valueToken.Val
|
||||
}
|
||||
|
||||
if arguments.MatchOne(TokenIdentifier, "reversed") != nil {
|
||||
forNode.reversed = true
|
||||
}
|
||||
|
||||
if arguments.MatchOne(TokenIdentifier, "sorted") != nil {
|
||||
forNode.sorted = true
|
||||
}
|
||||
|
||||
if arguments.Remaining() > 0 {
|
||||
return nil, arguments.Error("Malformed for-loop arguments.", nil)
|
||||
}
|
||||
|
||||
// Body wrapping
|
||||
wrapper, endargs, err := doc.WrapUntilTag("empty", "endfor")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
forNode.bodyWrapper = wrapper
|
||||
|
||||
if endargs.Count() > 0 {
|
||||
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||
}
|
||||
|
||||
if wrapper.Endtag == "empty" {
|
||||
// if there's an else in the if-statement, we need the else-Block as well
|
||||
wrapper, endargs, err = doc.WrapUntilTag("endfor")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
forNode.emptyWrapper = wrapper
|
||||
|
||||
if endargs.Count() > 0 {
|
||||
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return forNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("for", tagForParser)
|
||||
}
|
||||
76
vendor/github.com/flosch/pongo2/tags_if.go
generated
vendored
Normal file
76
vendor/github.com/flosch/pongo2/tags_if.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package pongo2
|
||||
|
||||
type tagIfNode struct {
|
||||
conditions []IEvaluator
|
||||
wrappers []*NodeWrapper
|
||||
}
|
||||
|
||||
func (node *tagIfNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
for i, condition := range node.conditions {
|
||||
result, err := condition.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.IsTrue() {
|
||||
return node.wrappers[i].Execute(ctx, writer)
|
||||
}
|
||||
// Last condition?
|
||||
if len(node.conditions) == i+1 && len(node.wrappers) > i+1 {
|
||||
return node.wrappers[i+1].Execute(ctx, writer)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
ifNode := &tagIfNode{}
|
||||
|
||||
// Parse first and main IF condition
|
||||
condition, err := arguments.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifNode.conditions = append(ifNode.conditions, condition)
|
||||
|
||||
if arguments.Remaining() > 0 {
|
||||
return nil, arguments.Error("If-condition is malformed.", nil)
|
||||
}
|
||||
|
||||
// Check the rest
|
||||
for {
|
||||
wrapper, tagArgs, err := doc.WrapUntilTag("elif", "else", "endif")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifNode.wrappers = append(ifNode.wrappers, wrapper)
|
||||
|
||||
if wrapper.Endtag == "elif" {
|
||||
// elif can take a condition
|
||||
condition, err = tagArgs.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifNode.conditions = append(ifNode.conditions, condition)
|
||||
|
||||
if tagArgs.Remaining() > 0 {
|
||||
return nil, tagArgs.Error("Elif-condition is malformed.", nil)
|
||||
}
|
||||
} else {
|
||||
if tagArgs.Count() > 0 {
|
||||
// else/endif can't take any conditions
|
||||
return nil, tagArgs.Error("Arguments not allowed here.", nil)
|
||||
}
|
||||
}
|
||||
|
||||
if wrapper.Endtag == "endif" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ifNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("if", tagIfParser)
|
||||
}
|
||||
116
vendor/github.com/flosch/pongo2/tags_ifchanged.go
generated
vendored
Normal file
116
vendor/github.com/flosch/pongo2/tags_ifchanged.go
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
package pongo2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type tagIfchangedNode struct {
|
||||
watchedExpr []IEvaluator
|
||||
lastValues []*Value
|
||||
lastContent []byte
|
||||
thenWrapper *NodeWrapper
|
||||
elseWrapper *NodeWrapper
|
||||
}
|
||||
|
||||
func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
if len(node.watchedExpr) == 0 {
|
||||
// Check against own rendered body
|
||||
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB
|
||||
err := node.thenWrapper.Execute(ctx, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bufBytes := buf.Bytes()
|
||||
if !bytes.Equal(node.lastContent, bufBytes) {
|
||||
// Rendered content changed, output it
|
||||
writer.Write(bufBytes)
|
||||
node.lastContent = bufBytes
|
||||
}
|
||||
} else {
|
||||
nowValues := make([]*Value, 0, len(node.watchedExpr))
|
||||
for _, expr := range node.watchedExpr {
|
||||
val, err := expr.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nowValues = append(nowValues, val)
|
||||
}
|
||||
|
||||
// Compare old to new values now
|
||||
changed := len(node.lastValues) == 0
|
||||
|
||||
for idx, oldVal := range node.lastValues {
|
||||
if !oldVal.EqualValueTo(nowValues[idx]) {
|
||||
changed = true
|
||||
break // we can stop here because ONE value changed
|
||||
}
|
||||
}
|
||||
|
||||
node.lastValues = nowValues
|
||||
|
||||
if changed {
|
||||
// Render thenWrapper
|
||||
err := node.thenWrapper.Execute(ctx, writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Render elseWrapper
|
||||
err := node.elseWrapper.Execute(ctx, writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
ifchangedNode := &tagIfchangedNode{}
|
||||
|
||||
for arguments.Remaining() > 0 {
|
||||
// Parse condition
|
||||
expr, err := arguments.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifchangedNode.watchedExpr = append(ifchangedNode.watchedExpr, expr)
|
||||
}
|
||||
|
||||
if arguments.Remaining() > 0 {
|
||||
return nil, arguments.Error("Ifchanged-arguments are malformed.", nil)
|
||||
}
|
||||
|
||||
// Wrap then/else-blocks
|
||||
wrapper, endargs, err := doc.WrapUntilTag("else", "endifchanged")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifchangedNode.thenWrapper = wrapper
|
||||
|
||||
if endargs.Count() > 0 {
|
||||
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||
}
|
||||
|
||||
if wrapper.Endtag == "else" {
|
||||
// if there's an else in the if-statement, we need the else-Block as well
|
||||
wrapper, endargs, err = doc.WrapUntilTag("endifchanged")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifchangedNode.elseWrapper = wrapper
|
||||
|
||||
if endargs.Count() > 0 {
|
||||
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return ifchangedNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("ifchanged", tagIfchangedParser)
|
||||
}
|
||||
78
vendor/github.com/flosch/pongo2/tags_ifequal.go
generated
vendored
Normal file
78
vendor/github.com/flosch/pongo2/tags_ifequal.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package pongo2
|
||||
|
||||
type tagIfEqualNode struct {
|
||||
var1, var2 IEvaluator
|
||||
thenWrapper *NodeWrapper
|
||||
elseWrapper *NodeWrapper
|
||||
}
|
||||
|
||||
func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error {
|
||||
r1, err := node.var1.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r2, err := node.var2.Evaluate(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := r1.EqualValueTo(r2)
|
||||
|
||||
if result {
|
||||
return node.thenWrapper.Execute(ctx, writer)
|
||||
}
|
||||
if node.elseWrapper != nil {
|
||||
return node.elseWrapper.Execute(ctx, writer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) {
|
||||
ifequalNode := &tagIfEqualNode{}
|
||||
|
||||
// Parse two expressions
|
||||
var1, err := arguments.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var2, err := arguments.ParseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifequalNode.var1 = var1
|
||||
ifequalNode.var2 = var2
|
||||
|
||||
if arguments.Remaining() > 0 {
|
||||
return nil, arguments.Error("ifequal only takes 2 arguments.", nil)
|
||||
}
|
||||
|
||||
// Wrap then/else-blocks
|
||||
wrapper, endargs, err := doc.WrapUntilTag("else", "endifequal")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifequalNode.thenWrapper = wrapper
|
||||
|
||||
if endargs.Count() > 0 {
|
||||
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||
}
|
||||
|
||||
if wrapper.Endtag == "else" {
|
||||
// if there's an else in the if-statement, we need the else-Block as well
|
||||
wrapper, endargs, err = doc.WrapUntilTag("endifequal")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifequalNode.elseWrapper = wrapper
|
||||
|
||||
if endargs.Count() > 0 {
|
||||
return nil, endargs.Error("Arguments not allowed here.", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return ifequalNode, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterTag("ifequal", tagIfEqualParser)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user