update rootlesskit to v0.10.0

Fix intermittent "Connection reset by peer" error during port forwarding

https://github.com/rootless-containers/rootlesskit/issues/153

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
This commit is contained in:
Akihiro Suda 2020-08-05 18:11:24 +09:00
parent c8282f4939
commit a70cdac356
No known key found for this signature in database
GPG Key ID: 49524C6F9F638F1A
245 changed files with 17091 additions and 90094 deletions

10
go.mod
View File

@ -86,7 +86,7 @@ require (
github.com/gogo/googleapis v1.3.0 // indirect
github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3
github.com/gorilla/mux v1.7.4
github.com/gorilla/websocket v1.4.1
github.com/kubernetes-sigs/cri-tools v0.0.0-00010101000000-000000000000
github.com/lib/pq v1.1.1
@ -102,16 +102,16 @@ require (
github.com/rancher/spur v0.0.0-20200617165101-8702c8e4ce7a
github.com/rancher/wrangler v0.6.1
github.com/rancher/wrangler-api v0.6.0
github.com/rootless-containers/rootlesskit v0.7.2
github.com/sirupsen/logrus v1.4.2
github.com/rootless-containers/rootlesskit v0.10.0
github.com/sirupsen/logrus v1.6.0
github.com/spf13/pflag v1.0.5
github.com/tchap/go-patricia v2.3.0+incompatible // indirect
github.com/urfave/cli v1.22.2
// 54ba958 is v3.4.9
go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3
google.golang.org/grpc v1.26.0
gopkg.in/yaml.v2 v2.3.0
k8s.io/api v0.18.5

30
go.sum
View File

@ -393,6 +393,8 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
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/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
@ -426,7 +428,7 @@ github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/insomniacslk/dhcp v0.0.0-20190712084813-dc1a53400564/go.mod h1:CfMdguCK66I5DAUJgGKyNz8aB6vO5dZzkm9Xep6WGvw=
github.com/insomniacslk/dhcp v0.0.0-20200420235442-ed3125c2efe7/go.mod h1:CfMdguCK66I5DAUJgGKyNz8aB6vO5dZzkm9Xep6WGvw=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/jamescun/tuntap v0.0.0-20190712092105-cb1fb277045c/go.mod h1:zzwpsgcYhzzIP5WyF8g9ivCv38cY9uAV9Gu0m3lThhE=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
@ -465,6 +467,8 @@ github.com/knative/serving v0.6.1/go.mod h1:ljvMfwQy2qanaM/8xnBSK4Mz3Vv2NawC2fo5
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -530,7 +534,9 @@ github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUb
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/vpnkit v0.3.1-0.20190720080441-7dd3dcce7d3d/go.mod h1:KyjUrL9cb6ZSNNAUwZfqRjhwwgJ3BJN+kXh0t43WTUQ=
github.com/moby/sys/mountinfo v0.1.3 h1:KIrhRO14+AkwKvG/g2yIpNMOUVZ02xNhOw8KY1WsLOI=
github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
github.com/moby/vpnkit v0.4.0/go.mod h1:KyjUrL9cb6ZSNNAUwZfqRjhwwgJ3BJN+kXh0t43WTUQ=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -696,8 +702,8 @@ github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfm
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.7.2 h1:gcWQ9/GN98ne1AqnoeOgQ8e6qpKd3BuB4ug+2h95Fr0=
github.com/rootless-containers/rootlesskit v0.7.2/go.mod h1:r9YL5mKRIdnwcYk4G8E5CSc9MDeFtgYmhfE4CSvDGYA=
github.com/rootless-containers/rootlesskit v0.10.0 h1:62HHP8s8qYYcolEtAsuo4GU6qau6pWmcQ1Te+TZTFds=
github.com/rootless-containers/rootlesskit v0.10.0/go.mod h1:OZQfuRPb+2MA1p+hmjHmSmDRv9SdTzlQ3taNA/0d7XM=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c h1:ht7N4d/B7Ezf58nvMNVF3OlvDlz9pp+WHVcRNS0nink=
github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto=
github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@ -723,6 +729,8 @@ github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjM
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
@ -758,6 +766,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
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/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@ -767,15 +776,13 @@ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cb
github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmDkqO9/zg7R0lSQRs=
github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tektoncd/pipeline v0.4.0/go.mod h1:IZzJdiX9EqEMuUcgdnElozdYYRh0/ZRC+NKMLj1K3Yw=
github.com/theckman/go-flock v0.7.1 h1:YdJyIjDuQdEU7voZ9YaeXSO4OnrxdI+WejPUwyZ/Txs=
github.com/theckman/go-flock v0.7.1/go.mod h1:kjuth3y9VJ2aNlkNEO99G/8lp9fMIKaGyBmh84IBheM=
github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLYM/iQ8KXej1AwM=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/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/u-root/u-root v5.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
github.com/u-root/u-root v6.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
github.com/ugorji/go v0.0.0-20170107133203-ded73eae5db7/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
@ -787,6 +794,8 @@ github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.0.0 h1:+HU9SCbu8GnEUFtIBfuUNXN39ofWViIEJIp6SURMpCg=
github.com/urfave/cli/v2 v2.0.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
@ -886,12 +895,13 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a h1:+HHJiFUXVOIS9mr1ThqkQD1N8vpFCfCShqADBM12KTc=
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -926,13 +936,15 @@ golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -105,8 +105,13 @@ func createParentOpt(stateDir string) (*parent.Opt, error) {
if _, err := exec.LookPath(binary); err != nil {
return nil, err
}
opt.NetworkDriver = slirp4netns.NewParentDriver(binary, mtu, ipnet, disableHostLoopback, "", false, false)
opt.PortDriver, err = portbuiltin.NewParentDriver(&logrusDebugWriter{}, stateDir)
debugWriter := &logrusDebugWriter{}
opt.NetworkDriver, err = slirp4netns.NewParentDriver(debugWriter, binary, mtu, ipnet, disableHostLoopback, "", false, false)
if err != nil {
return nil, err
}
opt.PortDriver, err = portbuiltin.NewParentDriver(debugWriter, stateDir)
if err != nil {
return nil, err
}

View File

@ -1,11 +1,10 @@
# gorilla/mux
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
[![CircleCI](https://circleci.com/gh/gorilla/mux.svg?style=svg)](https://circleci.com/gh/gorilla/mux)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
![Gorilla Logo](https://cloud-cdn.questionable.services/gorilla-icon-64.png)
https://www.gorillatoolkit.org/pkg/mux
@ -26,6 +25,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
* [Examples](#examples)
* [Matching Routes](#matching-routes)
* [Static Files](#static-files)
* [Serving Single Page Applications](#serving-single-page-applications) (e.g. React, Vue, Ember.js, etc.)
* [Registered URLs](#registered-urls)
* [Walking Routes](#walking-routes)
* [Graceful Shutdown](#graceful-shutdown)
@ -212,6 +212,93 @@ func main() {
}
```
### Serving Single Page Applications
Most of the time it makes sense to serve your SPA on a separate web server from your API,
but sometimes it's desirable to serve them both from one place. It's possible to write a simple
handler for serving your SPA (for use with React Router's [BrowserRouter](https://reacttraining.com/react-router/web/api/BrowserRouter) for example), and leverage
mux's powerful routing for your API endpoints.
```go
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"path/filepath"
"time"
"github.com/gorilla/mux"
)
// spaHandler implements the http.Handler interface, so we can use it
// to respond to HTTP requests. The path to the static directory and
// path to the index file within that static directory are used to
// serve the SPA in the given static directory.
type spaHandler struct {
staticPath string
indexPath string
}
// ServeHTTP inspects the URL path to locate a file within the static dir
// on the SPA handler. If a file is found, it will be served. If not, the
// file located at the index path on the SPA handler will be served. This
// is suitable behavior for serving an SPA (single page application).
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// get the absolute path to prevent directory traversal
path, err := filepath.Abs(r.URL.Path)
if err != nil {
// if we failed to get the absolute path respond with a 400 bad request
// and stop
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// prepend the path with the path to the static directory
path = filepath.Join(h.staticPath, path)
// check whether a file exists at the given path
_, err = os.Stat(path)
if os.IsNotExist(err) {
// file does not exist, serve index.html
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
return
} else if err != nil {
// if we got an error (that wasn't that the file doesn't exist) stating the
// file, return a 500 internal server error and stop
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// otherwise, use http.FileServer to serve the static dir
http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
// an example API handler
json.NewEncoder(w).Encode(map[string]bool{"ok": true})
})
spa := spaHandler{staticPath: "build", indexPath: "index.html"}
router.PathPrefix("/").Handler(spa)
srv := &http.Server{
Handler: router,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
```
### Registered URLs
Now let's see how to build registered URLs.

View File

@ -1,18 +0,0 @@
package mux
import (
"context"
"net/http"
)
func contextGet(r *http.Request, key interface{}) interface{} {
return r.Context().Value(key)
}
func contextSet(r *http.Request, key, val interface{}) *http.Request {
if val == nil {
return r
}
return r.WithContext(context.WithValue(r.Context(), key, val))
}

View File

@ -1 +1,3 @@
module github.com/gorilla/mux
go 1.12

View File

@ -58,22 +58,17 @@ func CORSMethodMiddleware(r *Router) MiddlewareFunc {
func getAllMethodsForRoute(r *Router, req *http.Request) ([]string, error) {
var allMethods []string
err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
for _, m := range route.matchers {
if _, ok := m.(*routeRegexp); ok {
if m.Match(req, &RouteMatch{}) {
for _, route := range r.routes {
var match RouteMatch
if route.Match(req, &match) || match.MatchErr == ErrMethodMismatch {
methods, err := route.GetMethods()
if err != nil {
return err
return nil, err
}
allMethods = append(allMethods, methods...)
}
break
}
}
return nil
})
return allMethods, err
return allMethods, nil
}

28
vendor/github.com/gorilla/mux/mux.go generated vendored
View File

@ -5,6 +5,7 @@
package mux
import (
"context"
"errors"
"fmt"
"net/http"
@ -58,8 +59,7 @@ type Router struct {
// If true, do not clear the request context after handling the request.
//
// Deprecated: No effect when go1.7+ is used, since the context is stored
// on the request itself.
// Deprecated: No effect, since the context is stored on the request itself.
KeepContext bool
// Slice of middlewares to be called after a match is found
@ -111,10 +111,8 @@ func copyRouteConf(r routeConf) routeConf {
c.regexp.queries = append(c.regexp.queries, copyRouteRegexp(q))
}
c.matchers = make([]matcher, 0, len(r.matchers))
for _, m := range r.matchers {
c.matchers = append(c.matchers, m)
}
c.matchers = make([]matcher, len(r.matchers))
copy(c.matchers, r.matchers)
return c
}
@ -197,8 +195,8 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var handler http.Handler
if r.Match(req, &match) {
handler = match.Handler
req = setVars(req, match.Vars)
req = setCurrentRoute(req, match.Route)
req = requestWithVars(req, match.Vars)
req = requestWithRoute(req, match.Route)
}
if handler == nil && match.MatchErr == ErrMethodMismatch {
@ -428,7 +426,7 @@ const (
// Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string {
if rv := contextGet(r, varsKey); rv != nil {
if rv := r.Context().Value(varsKey); rv != nil {
return rv.(map[string]string)
}
return nil
@ -440,18 +438,20 @@ func Vars(r *http.Request) map[string]string {
// after the handler returns, unless the KeepContext option is set on the
// Router.
func CurrentRoute(r *http.Request) *Route {
if rv := contextGet(r, routeKey); rv != nil {
if rv := r.Context().Value(routeKey); rv != nil {
return rv.(*Route)
}
return nil
}
func setVars(r *http.Request, val interface{}) *http.Request {
return contextSet(r, varsKey, val)
func requestWithVars(r *http.Request, vars map[string]string) *http.Request {
ctx := context.WithValue(r.Context(), varsKey, vars)
return r.WithContext(ctx)
}
func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
return contextSet(r, routeKey, val)
func requestWithRoute(r *http.Request, route *Route) *http.Request {
ctx := context.WithValue(r.Context(), routeKey, route)
return r.WithContext(ctx)
}
// ----------------------------------------------------------------------------

View File

@ -181,7 +181,8 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
}
}
return r.regexp.MatchString(host)
} else {
}
if r.regexpType == regexpTypeQuery {
return r.matchQueryString(req)
}
@ -191,11 +192,10 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
}
return r.regexp.MatchString(path)
}
}
// url builds a URL part using the given values.
func (r *routeRegexp) url(values map[string]string) (string, error) {
urlValues := make([]interface{}, len(r.varsN))
urlValues := make([]interface{}, len(r.varsN), len(r.varsN))
for k, v := range r.varsN {
value, ok := values[v]
if !ok {
@ -230,14 +230,51 @@ func (r *routeRegexp) getURLQuery(req *http.Request) string {
return ""
}
templateKey := strings.SplitN(r.template, "=", 2)[0]
for key, vals := range req.URL.Query() {
if key == templateKey && len(vals) > 0 {
return key + "=" + vals[0]
}
val, ok := findFirstQueryKey(req.URL.RawQuery, templateKey)
if ok {
return templateKey + "=" + val
}
return ""
}
// findFirstQueryKey returns the same result as (*url.URL).Query()[key][0].
// If key was not found, empty string and false is returned.
func findFirstQueryKey(rawQuery, key string) (value string, ok bool) {
query := []byte(rawQuery)
for len(query) > 0 {
foundKey := query
if i := bytes.IndexAny(foundKey, "&;"); i >= 0 {
foundKey, query = foundKey[:i], foundKey[i+1:]
} else {
query = query[:0]
}
if len(foundKey) == 0 {
continue
}
var value []byte
if i := bytes.IndexByte(foundKey, '='); i >= 0 {
foundKey, value = foundKey[:i], foundKey[i+1:]
}
if len(foundKey) < len(key) {
// Cannot possibly be key.
continue
}
keyString, err := url.QueryUnescape(string(foundKey))
if err != nil {
continue
}
if keyString != key {
continue
}
valueString, err := url.QueryUnescape(string(value))
if err != nil {
continue
}
return valueString, true
}
return "", false
}
func (r *routeRegexp) matchQueryString(req *http.Request) bool {
return r.regexp.MatchString(r.getURLQuery(req))
}

View File

@ -74,7 +74,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
return false
}
if match.MatchErr == ErrMethodMismatch {
if match.MatchErr == ErrMethodMismatch && r.handler != nil {
// We found a route which matches request method, clear MatchErr
match.MatchErr = nil
// Then override the mis-matched handler
@ -412,11 +412,30 @@ func (r *Route) Queries(pairs ...string) *Route {
type schemeMatcher []string
func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
return matchInArray(m, r.URL.Scheme)
scheme := r.URL.Scheme
// https://golang.org/pkg/net/http/#Request
// "For [most] server requests, fields other than Path and RawQuery will be
// empty."
// Since we're an http muxer, the scheme is either going to be http or https
// though, so we can just set it based on the tls termination state.
if scheme == "" {
if r.TLS == nil {
scheme = "http"
} else {
scheme = "https"
}
}
return matchInArray(m, scheme)
}
// Schemes adds a matcher for URL schemes.
// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
// If the request's URL has a scheme set, it will be matched against.
// Generally, the URL scheme will only be set if a previous handler set it,
// such as the ProxyHeaders handler from gorilla/handlers.
// If unset, the scheme will be determined based on the request's TLS
// termination state.
// The first argument to Schemes will be used when constructing a route URL.
func (r *Route) Schemes(schemes ...string) *Route {
for k, v := range schemes {
schemes[k] = strings.ToLower(v)
@ -493,8 +512,8 @@ func (r *Route) Subrouter() *Router {
// This also works for host variables:
//
// r := mux.NewRouter()
// r.Host("{subdomain}.domain.com").
// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
// Host("{subdomain}.domain.com").
// Name("article")
//
// // url.String() will be "http://news.domain.com/articles/technology/42"
@ -502,6 +521,13 @@ func (r *Route) Subrouter() *Router {
// "category", "technology",
// "id", "42")
//
// The scheme of the resulting url will be the first argument that was passed to Schemes:
//
// // url.String() will be "https://example.com"
// r := mux.NewRouter()
// url, err := r.Host("example.com")
// .Schemes("https", "http").URL()
//
// All variables defined in the route are required, and their values must
// conform to the corresponding patterns.
func (r *Route) URL(pairs ...string) (*url.URL, error) {
@ -635,7 +661,7 @@ func (r *Route) GetQueriesRegexp() ([]string, error) {
if r.regexp.queries == nil {
return nil, errors.New("mux: route doesn't have queries")
}
var queries []string
queries := make([]string, 0, len(r.regexp.queries))
for _, query := range r.regexp.queries {
queries = append(queries, query.regexp.String())
}
@ -654,7 +680,7 @@ func (r *Route) GetQueriesTemplates() ([]string, error) {
if r.regexp.queries == nil {
return nil, errors.New("mux: route doesn't have queries")
}
var queries []string
queries := make([]string, 0, len(r.regexp.queries))
for _, query := range r.regexp.queries {
queries = append(queries, query.template)
}

View File

@ -15,5 +15,5 @@ import "net/http"
// can be set by making a route that captures the required variables,
// starting a server and sending the request to that server.
func SetURLVars(r *http.Request, val map[string]string) *http.Request {
return setVars(r, val)
return requestWithVars(r, val)
}

View File

@ -27,6 +27,7 @@ We thank all the authors who provided code to this library:
* Felix Kollmann
* Nicolas Perraut
* @dirty49374
## License

View File

@ -4,7 +4,6 @@ package sequences
import (
"syscall"
"unsafe"
)
var (
@ -27,7 +26,7 @@ func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error {
mode &^= ENABLE_VIRTUAL_TERMINAL_PROCESSING
}
ret, _, err := setConsoleMode.Call(uintptr(unsafe.Pointer(stream)), uintptr(mode))
ret, _, err := setConsoleMode.Call(uintptr(stream), uintptr(mode))
if ret == 0 {
return err
}

202
vendor/github.com/moby/sys/mountinfo/LICENSE generated vendored Normal file
View File

@ -0,0 +1,202 @@
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.

3
vendor/github.com/moby/sys/mountinfo/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/moby/sys/mountinfo
go 1.14

67
vendor/github.com/moby/sys/mountinfo/mountinfo.go generated vendored Normal file
View File

@ -0,0 +1,67 @@
package mountinfo
import "io"
// GetMounts retrieves a list of mounts for the current running process,
// with an optional filter applied (use nil for no filter).
func GetMounts(f FilterFunc) ([]*Info, error) {
return parseMountTable(f)
}
// GetMountsFromReader retrieves a list of mounts from the
// reader provided, with an optional filter applied (use nil
// for no filter). This can be useful in tests or benchmarks
// that provide a fake mountinfo data.
func GetMountsFromReader(reader io.Reader, f FilterFunc) ([]*Info, error) {
return parseInfoFile(reader, f)
}
// Mounted determines if a specified mountpoint has been mounted.
// On Linux it looks at /proc/self/mountinfo.
func Mounted(mountpoint string) (bool, error) {
entries, err := GetMounts(SingleEntryFilter(mountpoint))
if err != nil {
return false, err
}
return len(entries) > 0, nil
}
// Info reveals information about a particular mounted filesystem. This
// struct is populated from the content in the /proc/<pid>/mountinfo file.
type Info struct {
// ID is a unique identifier of the mount (may be reused after umount).
ID int
// Parent indicates the ID of the mount parent (or of self for the top of the
// mount tree).
Parent int
// Major indicates one half of the device ID which identifies the device class.
Major int
// Minor indicates one half of the device ID which identifies a specific
// instance of device.
Minor int
// Root of the mount within the filesystem.
Root string
// Mountpoint indicates the mount point relative to the process's root.
Mountpoint string
// Opts represents mount-specific options.
Opts string
// Optional represents optional fields.
Optional string
// Fstype indicates the type of filesystem, such as EXT3.
Fstype string
// Source indicates filesystem specific information or "none".
Source string
// VfsOpts represents per super block options.
VfsOpts string
}

View File

@ -0,0 +1,58 @@
package mountinfo
import "strings"
// FilterFunc is a type defining a callback function for GetMount(),
// used to filter out mountinfo entries we're not interested in,
// and/or stop further processing if we found what we wanted.
//
// It takes a pointer to the Info struct (not fully populated,
// currently only Mountpoint, Fstype, Source, and (on Linux)
// VfsOpts are filled in), and returns two booleans:
//
// - skip: true if the entry should be skipped
// - stop: true if parsing should be stopped after the entry
type FilterFunc func(*Info) (skip, stop bool)
// PrefixFilter discards all entries whose mount points
// do not start with a specific prefix
func PrefixFilter(prefix string) FilterFunc {
return func(m *Info) (bool, bool) {
skip := !strings.HasPrefix(m.Mountpoint, prefix)
return skip, false
}
}
// SingleEntryFilter looks for a specific entry
func SingleEntryFilter(mp string) FilterFunc {
return func(m *Info) (bool, bool) {
if m.Mountpoint == mp {
return false, true // don't skip, stop now
}
return true, false // skip, keep going
}
}
// ParentsFilter returns all entries whose mount points
// can be parents of a path specified, discarding others.
//
// For example, given `/var/lib/docker/something`, entries
// like `/var/lib/docker`, `/var` and `/` are returned.
func ParentsFilter(path string) FilterFunc {
return func(m *Info) (bool, bool) {
skip := !strings.HasPrefix(path, m.Mountpoint)
return skip, false
}
}
// FstypeFilter returns all entries that match provided fstype(s).
func FstypeFilter(fstype ...string) FilterFunc {
return func(m *Info) (bool, bool) {
for _, t := range fstype {
if m.Fstype == t {
return false, false // don't skeep, keep going
}
}
return true, false // skip, keep going
}
}

View File

@ -0,0 +1,53 @@
package mountinfo
/*
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
*/
import "C"
import (
"fmt"
"reflect"
"unsafe"
)
// parseMountTable returns information about mounted filesystems
func parseMountTable(filter FilterFunc) ([]*Info, error) {
var rawEntries *C.struct_statfs
count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
if count == 0 {
return nil, fmt.Errorf("Failed to call getmntinfo")
}
var entries []C.struct_statfs
header := (*reflect.SliceHeader)(unsafe.Pointer(&entries))
header.Cap = count
header.Len = count
header.Data = uintptr(unsafe.Pointer(rawEntries))
var out []*Info
for _, entry := range entries {
var mountinfo Info
var skip, stop bool
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
if filter != nil {
// filter out entries we're not interested in
skip, stop = filter(&mountinfo)
if skip {
continue
}
}
out = append(out, &mountinfo)
if stop {
break
}
}
return out, nil
}

152
vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go generated vendored Normal file
View File

@ -0,0 +1,152 @@
// +build go1.13
package mountinfo
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
"strings"
)
func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
s := bufio.NewScanner(r)
out := []*Info{}
var err error
for s.Scan() {
if err = s.Err(); err != nil {
return nil, err
}
/*
See http://man7.org/linux/man-pages/man5/proc.5.html
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
(1) mount ID: unique identifier of the mount (may be reused after umount)
(2) parent ID: ID of parent (or of self for the top of the mount tree)
(3) major:minor: value of st_dev for files on filesystem
(4) root: root of the mount within the filesystem
(5) mount point: mount point relative to the process's root
(6) mount options: per mount options
(7) optional fields: zero or more fields of the form "tag[:value]"
(8) separator: marks the end of the optional fields
(9) filesystem type: name of filesystem of the form "type[.subtype]"
(10) mount source: filesystem specific information or "none"
(11) super options: per super block options
In other words, we have:
* 6 mandatory fields (1)..(6)
* 0 or more optional fields (7)
* a separator field (8)
* 3 mandatory fields (9)..(11)
*/
text := s.Text()
fields := strings.Split(text, " ")
numFields := len(fields)
if numFields < 10 {
// should be at least 10 fields
return nil, fmt.Errorf("Parsing '%s' failed: not enough fields (%d)", text, numFields)
}
// separator field
sepIdx := numFields - 4
// In Linux <= 3.9 mounting a cifs with spaces in a share
// name (like "//srv/My Docs") _may_ end up having a space
// in the last field of mountinfo (like "unc=//serv/My Docs").
// Since kernel 3.10-rc1, cifs option "unc=" is ignored,
// so spaces should not appear.
//
// Check for a separator, and work around the spaces bug
for fields[sepIdx] != "-" {
sepIdx--
if sepIdx == 5 {
return nil, fmt.Errorf("Parsing '%s' failed: missing - separator", text)
}
}
p := &Info{}
// Fill in the fields that a filter might check
p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`)
if err != nil {
return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote mount point field: %w", fields[4], err)
}
p.Fstype = fields[sepIdx+1]
p.Source = fields[sepIdx+2]
p.VfsOpts = fields[sepIdx+3]
// Run a filter soon so we can skip parsing/adding entries
// the caller is not interested in
var skip, stop bool
if filter != nil {
skip, stop = filter(p)
if skip {
continue
}
}
// Fill in the rest of the fields
// ignore any numbers parsing errors, as there should not be any
p.ID, _ = strconv.Atoi(fields[0])
p.Parent, _ = strconv.Atoi(fields[1])
mm := strings.Split(fields[2], ":")
if len(mm) != 2 {
return nil, fmt.Errorf("Parsing '%s' failed: unexpected minor:major pair %s", text, mm)
}
p.Major, _ = strconv.Atoi(mm[0])
p.Minor, _ = strconv.Atoi(mm[1])
p.Root, err = strconv.Unquote(`"` + fields[3] + `"`)
if err != nil {
return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote root field: %w", fields[3], err)
}
p.Opts = fields[5]
// zero or more optional fields
switch {
case sepIdx == 6:
// zero, do nothing
case sepIdx == 7:
p.Optional = fields[6]
default:
p.Optional = strings.Join(fields[6:sepIdx-1], " ")
}
out = append(out, p)
if stop {
break
}
}
return out, nil
}
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
// bind mounts
func parseMountTable(filter FilterFunc) ([]*Info, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return nil, err
}
defer f.Close()
return parseInfoFile(f, filter)
}
// PidMountInfo collects the mounts for a specific process ID. If the process
// ID is unknown, it is better to use `GetMounts` which will inspect
// "/proc/self/mountinfo" instead.
func PidMountInfo(pid int) ([]*Info, error) {
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
if err != nil {
return nil, err
}
defer f.Close()
return parseInfoFile(f, nil)
}

View File

@ -0,0 +1,17 @@
// +build !windows,!linux,!freebsd freebsd,!cgo
package mountinfo
import (
"fmt"
"io"
"runtime"
)
func parseMountTable(_ FilterFunc) ([]*Info, error) {
return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
return parseMountTable(f)
}

View File

@ -0,0 +1,12 @@
package mountinfo
import "io"
func parseMountTable(_ FilterFunc) ([]*Info, error) {
// Do NOT return an error!
return nil, nil
}
func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
return parseMountTable(f)
}

View File

@ -0,0 +1,6 @@
package api
// ErrorJSON is returned with "application/json" content type and non-2XX status code
type ErrorJSON struct {
Message string `json:"message"`
}

View File

@ -12,8 +12,8 @@ import (
"os"
"github.com/pkg/errors"
"golang.org/x/net/context/ctxhttp"
"github.com/rootless-containers/rootlesskit/pkg/api"
"github.com/rootless-containers/rootlesskit/pkg/port"
)
@ -80,13 +80,40 @@ func readAtMost(r io.Reader, maxBytes int) ([]byte, error) {
return b, nil
}
// HTTPStatusErrorBodyMaxLength specifies the maximum length of HTTPStatusError.Body
const HTTPStatusErrorBodyMaxLength = 64 * 1024
// HTTPStatusError is created from non-2XX HTTP response
type HTTPStatusError struct {
// StatusCode is non-2XX status code
StatusCode int
// Body is at most HTTPStatusErrorBodyMaxLength
Body string
}
// Error implements error.
// If e.Body is a marshalled string of api.ErrorJSON, Error returns ErrorJSON.Message .
// Otherwise Error returns a human-readable string that contains e.StatusCode and e.Body.
func (e *HTTPStatusError) Error() string {
if e.Body != "" && len(e.Body) < HTTPStatusErrorBodyMaxLength {
var ej api.ErrorJSON
if json.Unmarshal([]byte(e.Body), &ej) == nil {
return ej.Message
}
}
return fmt.Sprintf("unexpected HTTP status %s, body=%q", http.StatusText(e.StatusCode), e.Body)
}
func successful(resp *http.Response) error {
if resp == nil {
return errors.New("nil response")
}
if resp.StatusCode/100 != 2 {
b, _ := readAtMost(resp.Body, 64*1024)
return errors.Errorf("unexpected HTTP status %s, body=%q", resp.Status, string(b))
b, _ := readAtMost(resp.Body, HTTPStatusErrorBodyMaxLength)
return &HTTPStatusError{
StatusCode: resp.StatusCode,
Body: string(b),
}
}
return nil
}
@ -101,7 +128,13 @@ func (pm *portManager) AddPort(ctx context.Context, spec port.Spec) (*port.Statu
return nil, err
}
u := fmt.Sprintf("http://%s/%s/ports", pm.client.dummyHost, pm.client.version)
resp, err := ctxhttp.Post(ctx, pm.client.HTTPClient(), u, "application/json", bytes.NewReader(m))
req, err := http.NewRequest("POST", u, bytes.NewReader(m))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
req = req.WithContext(ctx)
resp, err := pm.client.HTTPClient().Do(req)
if err != nil {
return nil, err
}
@ -118,7 +151,12 @@ func (pm *portManager) AddPort(ctx context.Context, spec port.Spec) (*port.Statu
}
func (pm *portManager) ListPorts(ctx context.Context) ([]port.Status, error) {
u := fmt.Sprintf("http://%s/%s/ports", pm.client.dummyHost, pm.client.version)
resp, err := ctxhttp.Get(ctx, pm.client.HTTPClient(), u)
req, err := http.NewRequest("GET", u, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
resp, err := pm.client.HTTPClient().Do(req)
if err != nil {
return nil, err
}
@ -139,7 +177,8 @@ func (pm *portManager) RemovePort(ctx context.Context, id int) error {
if err != nil {
return err
}
resp, err := ctxhttp.Do(ctx, pm.client.HTTPClient(), req)
req = req.WithContext(ctx)
resp, err := pm.client.HTTPClient().Do(req)
if err != nil {
return err
}

View File

@ -0,0 +1,82 @@
# When you made a change to this YAML, please validate with https://editor.swagger.io
openapi: 3.0.2
info:
version: 1.0.0
title: RootlessKit API
servers:
- url: 'http://rootlesskit/v1'
description: Local UNIX socket server. The host part of the URL is ignored.
paths:
/ports:
get:
responses:
'200':
description: An array of PortStatus
content:
application/json:
schema:
$ref: '#/components/schemas/PortStatuses'
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PortSpec'
responses:
'201':
description: PortStatus with ID
content:
application/json:
schema:
$ref: '#/components/schemas/PortStatus'
'/ports/{id}':
delete:
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: Null response
components:
schemas:
PortSpec:
required:
- proto
properties:
proto:
type: string
enum:
- tcp
- udp
- sctp
parentIP:
type: string
parentPort:
type: integer
format: int32
minimum: 1
maximum: 65535
# future version may support requests with parentPort<=0 for automatic port assignment
childPort:
type: integer
format: int32
minimum: 1
maximum: 65535
PortStatus:
required:
- id
properties:
id:
type: integer
format: int64
spec:
$ref: '#/components/schemas/PortSpec'
PortStatuses:
type: array
items:
$ref: '#/components/schemas/PortStatus'

View File

@ -9,6 +9,7 @@ import (
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/rootless-containers/rootlesskit/pkg/api"
"github.com/rootless-containers/rootlesskit/pkg/port"
)
@ -22,10 +23,7 @@ func (b *Backend) onError(w http.ResponseWriter, r *http.Request, err error, ec
w.WriteHeader(ec)
w.Header().Set("Content-Type", "application/json")
// it is safe to return the err to the client, because the client is reliable
type errorJSON struct {
Message string `json:"message"`
}
e := errorJSON{
e := api.ErrorJSON{
Message: err.Error(),
}
_ = json.NewEncoder(w).Encode(e)

View File

@ -1,6 +1,7 @@
package child
import (
"context"
"io/ioutil"
"os"
"os/exec"
@ -18,8 +19,19 @@ import (
"github.com/rootless-containers/rootlesskit/pkg/msgutil"
"github.com/rootless-containers/rootlesskit/pkg/network"
"github.com/rootless-containers/rootlesskit/pkg/port"
"github.com/rootless-containers/rootlesskit/pkg/sigproxy"
sigproxysignal "github.com/rootless-containers/rootlesskit/pkg/sigproxy/signal"
)
var propagationStates = map[string]uintptr{
"private": uintptr(unix.MS_PRIVATE),
"rprivate": uintptr(unix.MS_REC | unix.MS_PRIVATE),
"shared": uintptr(unix.MS_SHARED),
"rshared": uintptr(unix.MS_REC | unix.MS_SHARED),
"slave": uintptr(unix.MS_SLAVE),
"rslave": uintptr(unix.MS_REC | unix.MS_SLAVE),
}
func createCmd(targetCmd []string) (*exec.Cmd, error) {
var args []string
if len(targetCmd) > 1 {
@ -166,6 +178,7 @@ type Opt struct {
CopyUpDirs []string
PortDriver port.ChildDriver
MountProcfs bool // needs to be set if (and only if) parent.Opt.CreatePIDNS is set
Propagation string // mount propagation type
Reaper bool
}
@ -214,6 +227,9 @@ func Child(opt Opt) error {
if msg.StateDir == "" {
return errors.New("got empty StateDir")
}
if err := setMountPropagation(opt.Propagation); err != nil {
return err
}
etcWasCopied, err := setupCopyDir(opt.CopyUpDriver, opt.CopyUpDirs)
if err != nil {
return err
@ -243,7 +259,12 @@ func Child(opt Opt) error {
return errors.Wrapf(err, "command %v exited", opt.TargetCmd)
}
} else {
if err := cmd.Run(); err != nil {
if err := cmd.Start(); err != nil {
return errors.Wrapf(err, "command %v exited", opt.TargetCmd)
}
sigc := sigproxy.ForwardAllSignals(context.TODO(), cmd.Process.Pid)
defer sigproxysignal.StopCatch(sigc)
if err := cmd.Wait(); err != nil {
return errors.Wrapf(err, "command %v exited", opt.TargetCmd)
}
}
@ -254,12 +275,25 @@ func Child(opt Opt) error {
return nil
}
func setMountPropagation(propagation string) error {
flags, ok := propagationStates[propagation]
if ok {
if err := unix.Mount("none", "/", "", flags, ""); err != nil {
return errors.Wrapf(err, "failed to share mount point: /")
}
}
return nil
}
func runAndReap(cmd *exec.Cmd) error {
c := make(chan os.Signal, 32)
signal.Notify(c, syscall.SIGCHLD)
if err := cmd.Start(); err != nil {
return err
}
sigc := sigproxy.ForwardAllSignals(context.TODO(), cmd.Process.Pid)
defer sigproxysignal.StopCatch(sigc)
result := make(chan error)
go func() {
defer close(result)

View File

@ -2,6 +2,7 @@ package slirp4netns
import (
"context"
"io"
"net"
"os"
"os/exec"
@ -49,9 +50,14 @@ func DetectFeatures(binary string) (*Features, error) {
s := string(b)
if err != nil {
return nil, errors.Wrapf(err,
"command \"%s --help\" failed, make sure slirp4netns v0.2.0+ is installed: %q",
"command \"%s --help\" failed, make sure slirp4netns v0.4.0+ is installed: %q",
realBinary, s)
}
if !strings.Contains(s, "--netns-type") {
// We don't use --netns-type, but we check the presence of --netns-type to
// ensure slirp4netns >= v0.4.0: https://github.com/rootless-containers/rootlesskit/issues/143
return nil, errors.New("slirp4netns seems older than v0.4.0")
}
kernelSupportsEnableSeccomp := false
if unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0) != unix.EINVAL {
kernelSupportsEnableSeccomp = unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0) != unix.EINVAL
@ -68,24 +74,42 @@ func DetectFeatures(binary string) (*Features, error) {
}
// NewParentDriver instantiates new parent driver.
// ipnet is supported only for slirp4netns v0.3.0+.
// ipnet MUST be nil for slirp4netns < v0.3.0.
//
// disableHostLoopback is supported only for slirp4netns v0.3.0+
// apiSocketPath is supported only for slirp4netns v0.3.0+
// enableSandbox is supported only for slirp4netns v0.4.0+
// enableSeccomp is supported only for slirp4netns v0.4.0+
func NewParentDriver(binary string, mtu int, ipnet *net.IPNet, disableHostLoopback bool, apiSocketPath string, enableSandbox, enableSeccomp bool) network.ParentDriver {
// Requires slirp4netns v0.4.0 or later.
func NewParentDriver(logWriter io.Writer, binary string, mtu int, ipnet *net.IPNet, disableHostLoopback bool, apiSocketPath string, enableSandbox, enableSeccomp bool) (network.ParentDriver, error) {
if binary == "" {
panic("got empty slirp4netns binary")
return nil, errors.New("got empty slirp4netns binary")
}
if mtu < 0 {
panic("got negative mtu")
return nil, errors.New("got negative mtu")
}
if mtu == 0 {
mtu = 65520
}
features, err := DetectFeatures(binary)
if err != nil {
return nil, err
}
if ipnet != nil && !features.SupportsCIDR {
return nil, errors.New("this version of slirp4netns does not support --cidr")
}
if disableHostLoopback && !features.SupportsDisableHostLoopback {
return nil, errors.New("this version of slirp4netns does not support --disable-host-loopback")
}
if apiSocketPath != "" && !features.SupportsAPISocket {
return nil, errors.New("this version of slirp4netns does not support --api-socket")
}
if enableSandbox && !features.SupportsEnableSandbox {
return nil, errors.New("this version of slirp4netns does not support --enable-sandbox")
}
if enableSeccomp && !features.SupportsEnableSeccomp {
return nil, errors.New("this version of slirp4netns does not support --enable-seccomp")
}
if enableSeccomp && !features.KernelSupportsEnableSeccomp {
return nil, errors.New("kernel does not support seccomp")
}
return &parentDriver{
logWriter: logWriter,
binary: binary,
mtu: mtu,
ipnet: ipnet,
@ -93,10 +117,11 @@ func NewParentDriver(binary string, mtu int, ipnet *net.IPNet, disableHostLoopba
apiSocketPath: apiSocketPath,
enableSandbox: enableSandbox,
enableSeccomp: enableSeccomp,
}
}, nil
}
type parentDriver struct {
logWriter io.Writer
binary string
mtu int
ipnet *net.IPNet
@ -123,7 +148,7 @@ func (d *parentDriver) ConfigureNetwork(childPID int, stateDir string) (*common.
}
defer readyR.Close()
defer readyW.Close()
// -r: readyFD
// -r: readyFD (requires slirp4netns >= v0.4.0: https://github.com/rootless-containers/rootlesskit/issues/143)
opts := []string{"--mtu", strconv.Itoa(d.mtu), "-r", "3"}
if d.disableHostLoopback {
opts = append(opts, "--disable-host-loopback")
@ -141,6 +166,9 @@ func (d *parentDriver) ConfigureNetwork(childPID int, stateDir string) (*common.
opts = append(opts, "--enable-seccomp")
}
cmd := exec.CommandContext(ctx, d.binary, append(opts, []string{strconv.Itoa(childPID), tap}...)...)
// FIXME: Stdout doen't seem captured
cmd.Stdout = d.logWriter
cmd.Stderr = d.logWriter
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}

View File

@ -1,11 +1,10 @@
// Package idtools is forked from https://github.com/moby/moby/tree/c12f09bf99b54f274a5ae241dd154fa74020cbab/pkg/idtools
// Package idtools is forked from https://github.com/moby/moby/tree/298ba5b13150bfffe8414922a951a7a793276d31/pkg/idtools
package idtools
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
)
@ -97,20 +96,20 @@ type IdentityMapping struct {
// NewIdentityMapping takes a requested user and group name and
// using the data from /etc/sub{uid,gid} ranges, creates the
// proper uid and gid remapping ranges for that user/group pair
func NewIdentityMapping(username, groupname string) (*IdentityMapping, error) {
subuidRanges, err := parseSubuid(username)
func NewIdentityMapping(uid int, username string) (*IdentityMapping, error) {
subuidRanges, err := parseSubuid(uid, username)
if err != nil {
return nil, err
}
subgidRanges, err := parseSubgid(groupname)
subgidRanges, err := parseSubgid(uid, username)
if err != nil {
return nil, err
}
if len(subuidRanges) == 0 {
return nil, fmt.Errorf("No subuid ranges found for user %q", username)
return nil, fmt.Errorf("No subuid ranges found for user %d (%q)", uid, username)
}
if len(subgidRanges) == 0 {
return nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
return nil, fmt.Errorf("No subgid ranges found for user %d (%q)", uid, username)
}
return &IdentityMapping{
@ -182,8 +181,6 @@ func (i *IdentityMapping) GIDs() []IDMap {
func createIDMap(subidRanges ranges) []IDMap {
idMap := []IDMap{}
// sort the ranges by lowest ID first
sort.Sort(subidRanges)
containerID := 0
for _, idrange := range subidRanges {
idMap = append(idMap, IDMap{
@ -196,18 +193,18 @@ func createIDMap(subidRanges ranges) []IDMap {
return idMap
}
func parseSubuid(username string) (ranges, error) {
return parseSubidFile(subuidFileName, username)
func parseSubuid(uid int, username string) (ranges, error) {
return parseSubidFile(subuidFileName, uid, username)
}
func parseSubgid(username string) (ranges, error) {
return parseSubidFile(subgidFileName, username)
func parseSubgid(uid int, username string) (ranges, error) {
return parseSubidFile(subgidFileName, uid, username)
}
// parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
// and return all found ranges for a specified username. If the special value
// "ALL" is supplied for username, then all ranges in the file will be returned
func parseSubidFile(path, username string) (ranges, error) {
// and return all found ranges for a specified user. username is optional.
func parseSubidFile(path string, uid int, username string) (ranges, error) {
uidS := strconv.Itoa(uid)
var rangeList ranges
subidFile, err := os.Open(path)
@ -218,10 +215,6 @@ func parseSubidFile(path, username string) (ranges, error) {
s := bufio.NewScanner(subidFile)
for s.Scan() {
if err := s.Err(); err != nil {
return rangeList, err
}
text := strings.TrimSpace(s.Text())
if text == "" || strings.HasPrefix(text, "#") {
continue
@ -230,7 +223,7 @@ func parseSubidFile(path, username string) (ranges, error) {
if len(parts) != 3 {
return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
}
if parts[0] == username || username == "ALL" {
if parts[0] == uidS || (username != "" && parts[0] == username) {
startid, err := strconv.Atoi(parts[1])
if err != nil {
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
@ -242,5 +235,5 @@ func parseSubidFile(path, username string) (ranges, error) {
rangeList = append(rangeList, subIDRange{startid, length})
}
}
return rangeList, nil
return rangeList, s.Err()
}

View File

@ -2,6 +2,7 @@ package parent
import (
"context"
"fmt"
"io/ioutil"
"net"
"net/http"
@ -12,10 +13,11 @@ import (
"strconv"
"syscall"
"github.com/gofrs/flock"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/theckman/go-flock"
"golang.org/x/sys/unix"
"github.com/rootless-containers/rootlesskit/pkg/api/router"
"github.com/rootless-containers/rootlesskit/pkg/common"
@ -23,6 +25,8 @@ import (
"github.com/rootless-containers/rootlesskit/pkg/network"
"github.com/rootless-containers/rootlesskit/pkg/parent/idtools"
"github.com/rootless-containers/rootlesskit/pkg/port"
"github.com/rootless-containers/rootlesskit/pkg/sigproxy"
"github.com/rootless-containers/rootlesskit/pkg/sigproxy/signal"
)
type Opt struct {
@ -33,6 +37,12 @@ type Opt struct {
PortDriver port.ParentDriver // nil for --port-driver=none
PublishPorts []port.Spec
CreatePIDNS bool
CreateCgroupNS bool
CreateUTSNS bool
CreateIPCNS bool
ParentEUIDEnvKey string // optional env key to propagate geteuid() value
ParentEGIDEnvKey string // optional env key to propagate getegid() value
Propagation string
}
// Documented state files. Undocumented ones are subject to change.
@ -42,7 +52,7 @@ const (
StateFileAPISock = "api.sock" // REST API Socket
)
func Parent(opt Opt) error {
func checkPreflight(opt Opt) error {
if opt.PipeFDEnvKey == "" {
return errors.New("pipe FD env key is not set")
}
@ -55,6 +65,22 @@ func Parent(opt Opt) error {
if stat, err := os.Stat(opt.StateDir); err != nil || !stat.IsDir() {
return errors.Wrap(err, "state dir is inaccessible")
}
if os.Geteuid() == 0 {
logrus.Warn("Running RootlessKit as the root user is unsupported.")
}
warnSysctl()
// invalid propagation doesn't result in an error
warnPropagation(opt.Propagation)
return nil
}
func Parent(opt Opt) error {
if err := checkPreflight(opt); err != nil {
return err
}
lockPath := filepath.Join(opt.StateDir, StateFileLock)
lock := flock.NewFlock(lockPath)
locked, err := lock.TryLock()
@ -82,8 +108,7 @@ func Parent(opt Opt) error {
cmd := exec.Command("/proc/self/exe", os.Args[1:]...)
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
Cloneflags: syscall.CLONE_NEWUSER,
Unshareflags: syscall.CLONE_NEWNS,
Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS,
}
if opt.NetworkDriver != nil {
cmd.SysProcAttr.Unshareflags |= syscall.CLONE_NEWNET
@ -92,6 +117,15 @@ func Parent(opt Opt) error {
// cannot be Unshareflags (panics)
cmd.SysProcAttr.Cloneflags |= syscall.CLONE_NEWPID
}
if opt.CreateCgroupNS {
cmd.SysProcAttr.Unshareflags |= unix.CLONE_NEWCGROUP
}
if opt.CreateUTSNS {
cmd.SysProcAttr.Unshareflags |= unix.CLONE_NEWUTS
}
if opt.CreateIPCNS {
cmd.SysProcAttr.Unshareflags |= unix.CLONE_NEWIPC
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@ -100,12 +134,21 @@ func Parent(opt Opt) error {
if opt.StateDirEnvKey != "" {
cmd.Env = append(cmd.Env, opt.StateDirEnvKey+"="+opt.StateDir)
}
if opt.ParentEUIDEnvKey != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", opt.ParentEUIDEnvKey, os.Geteuid()))
}
if opt.ParentEGIDEnvKey != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", opt.ParentEGIDEnvKey, os.Getegid()))
}
if err := cmd.Start(); err != nil {
return errors.Wrap(err, "failed to start the child")
}
if err := setupUIDGIDMap(cmd.Process.Pid); err != nil {
return errors.Wrap(err, "failed to setup UID/GID map")
}
sigc := sigproxy.ForwardAllSignals(context.TODO(), cmd.Process.Pid)
defer signal.StopCatch(sigc)
// send message 0
msg := common.Message{
Stage: 0,
@ -216,11 +259,11 @@ func newugidmapArgs() ([]string, []string, error) {
"1",
}
// get both subid maps
// uses username for groupname in case primary groupname is not the same
// idtools will fall back to getent if /etc/passwd does not contain username
// works with external auth, ie sssd, ldap, nis
ims, err := idtools.NewIdentityMapping(u.Username, u.Username)
uid, err := strconv.Atoi(u.Uid)
if err != nil {
return nil, nil, err
}
ims, err := idtools.NewIdentityMapping(uid, u.Username)
if err != nil {
return nil, nil, err
}

View File

@ -0,0 +1,59 @@
package parent
import (
"io/ioutil"
"strconv"
"strings"
"github.com/moby/sys/mountinfo"
"github.com/sirupsen/logrus"
)
func warnPropagation(propagation string) {
mounts, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter("/"))
if err != nil || len(mounts) < 1 {
logrus.WithError(err).Warn("Failed to parse mountinfo")
return
}
root := mounts[0]
// 1. When running on a "sane" host, root.Optional is like "shared:1". ("shared" in findmnt(8) output)
// 2. When running inside a container, root.Optional is like "master:363". ("private, slave" in findmnt(8) output)
//
// Setting non-private propagation is supported for 1, unsupported for 2.
if !strings.Contains(propagation, "private") && !strings.Contains(root.Optional, "shared") {
logrus.Warnf("The host root filesystem is mounted as %q. Setting child propagation to %q is not supported.",
root.Optional, propagation)
}
}
// warnSysctl verifies /proc/sys/kernel/unprivileged_userns_clone and /proc/sys/user/max_user_namespaces
func warnSysctl() {
uuc, err := ioutil.ReadFile("/proc/sys/kernel/unprivileged_userns_clone")
// The file exists only on distros with the "add sysctl to disallow unprivileged CLONE_NEWUSER by default" patch.
// (e.g. Debian and Arch)
if err == nil {
s := strings.TrimSpace(string(uuc))
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
logrus.WithError(err).Warnf("Failed to parse /proc/sys/kernel/unprivileged_userns_clone (%q)", s)
} else if i == 0 {
logrus.Warn("/proc/sys/kernel/unprivileged_userns_clone needs to be set to 1.")
}
}
mun, err := ioutil.ReadFile("/proc/sys/user/max_user_namespaces")
if err == nil {
s := strings.TrimSpace(string(mun))
i, err := strconv.ParseInt(strings.TrimSpace(string(mun)), 10, 64)
if err != nil {
logrus.WithError(err).Warnf("Failed to parse /proc/sys/user/max_user_namespaces (%q)", s)
} else if i == 0 {
logrus.Warn("/proc/sys/user/max_user_namespaces needs to be set to non-zero.")
} else {
threshold := int64(1024)
if i < threshold {
logrus.Warnf("/proc/sys/user/max_user_namespaces=%d may be low. Consider setting to >= %d.", i, threshold)
}
}
}
}

View File

@ -119,12 +119,19 @@ func (d *childDriver) handleConnectRequest(c *net.UnixConn, req *msg.Request) er
if err != nil {
return err
}
defer targetConnFile.Close()
oob := unix.UnixRights(int(targetConnFile.Fd()))
f, err := c.File()
if err != nil {
return err
}
defer f.Close()
for {
err = unix.Sendmsg(int(f.Fd()), []byte("dummy"), oob, nil, 0)
if err != unix.EINTR {
break
}
}
return err
}

View File

@ -62,7 +62,16 @@ func ConnectToChild(c *net.UnixConn, spec port.Spec) (int, error) {
}
oobSpace := unix.CmsgSpace(4)
oob := make([]byte, oobSpace)
_, oobN, _, _, err := c.ReadMsgUnix(nil, oob)
var (
oobN int
err error
)
for {
_, oobN, _, _, err = c.ReadMsgUnix(nil, oob)
if err != unix.EINTR {
break
}
}
if err != nil {
return 0, err
}

View File

@ -2,11 +2,14 @@ package parent
import (
"context"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
@ -84,6 +87,40 @@ func (d *driver) RunParentDriver(initComplete chan struct{}, quit <-chan struct{
return nil
}
func isEPERM(err error) bool {
k := "permission denied"
// As of Go 1.14, errors.Is(err, syscall.EPERM) does not seem to work for
// "listen tcp 0.0.0.0:80: bind: permission denied" error from net.ListenTCP().
return errors.Is(err, syscall.EPERM) || strings.Contains(err.Error(), k)
}
// annotateEPERM annotates origErr for human-readability
func annotateEPERM(origErr error, spec port.Spec) error {
// Read "net.ipv4.ip_unprivileged_port_start" value (typically 1024)
// TODO: what for IPv6?
// NOTE: sync.Once should not be used here
b, e := ioutil.ReadFile("/proc/sys/net/ipv4/ip_unprivileged_port_start")
if e != nil {
return origErr
}
start, e := strconv.Atoi(strings.TrimSpace(string(b)))
if e != nil {
return origErr
}
if spec.ParentPort >= start {
// origErr is unrelated to ip_unprivileged_port_start
return origErr
}
text := fmt.Sprintf("cannot expose privileged port %d, you might need to add \"net.ipv4.ip_unprivileged_port_start=0\" (currently %d) to /etc/sysctl.conf", spec.ParentPort, start)
if filepath.Base(os.Args[0]) == "rootlesskit" {
// NOTE: The following sentence is appended only if Args[0] == "rootlesskit", because it does not apply to Podman (as of Podman v1.9).
// Podman launches the parent driver in the child user namespace (but in the parent network namespace), which disables the file capability.
text += ", or set CAP_NET_BIND_SERVICE on rootlesskit binary"
}
text += fmt.Sprintf(", or choose a larger port number (>= %d)", start)
return errors.Wrap(origErr, text)
}
func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) {
d.mu.Lock()
err := portutil.ValidatePortSpec(spec, d.ports)
@ -106,6 +143,9 @@ func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, err
return nil, errors.New("spec was not validated?")
}
if err != nil {
if isEPERM(err) {
err = annotateEPERM(err, spec)
}
return nil, err
}
d.mu.Lock()

View File

@ -58,8 +58,7 @@ func ValidatePortSpec(spec port.Spec, existingPorts map[int]*port.Status) error
sp := p.Spec
sameProto := sp.Proto == spec.Proto
sameParent := sp.ParentIP == spec.ParentIP && sp.ParentPort == spec.ParentPort
sameChild := sp.ChildPort == spec.ChildPort
if sameProto && (sameParent || sameChild) {
if sameProto && sameParent {
return errors.Errorf("conflict with ID %d", id)
}
}

View File

@ -0,0 +1,25 @@
// Package signal provides helper functions for dealing with signals across
// various operating systems.
//
// Forked from https://github.com/moby/moby/tree/37defbfd9b968f38e8e15dfa5f06d9f878bd65ba/pkg/signal
package signal
import (
"os"
"os/signal"
)
// CatchAll catches all signals and relays them to the specified channel.
func CatchAll(sigc chan os.Signal) {
var handledSigs []os.Signal
for _, s := range SignalMap {
handledSigs = append(handledSigs, s)
}
signal.Notify(sigc, handledSigs...)
}
// StopCatch stops catching the signals and closes the specified channel.
func StopCatch(sigc chan os.Signal) {
signal.Stop(sigc)
close(sigc)
}

View File

@ -0,0 +1,83 @@
// +build !mips,!mipsle,!mips64,!mips64le
package signal
import (
"syscall"
"golang.org/x/sys/unix"
)
const (
sigrtmin = 34
sigrtmax = 64
)
// SignalMap is a map of Linux signals.
var SignalMap = map[string]syscall.Signal{
"ABRT": unix.SIGABRT,
"ALRM": unix.SIGALRM,
"BUS": unix.SIGBUS,
"CHLD": unix.SIGCHLD,
"CLD": unix.SIGCLD,
"CONT": unix.SIGCONT,
"FPE": unix.SIGFPE,
"HUP": unix.SIGHUP,
"ILL": unix.SIGILL,
"INT": unix.SIGINT,
"IO": unix.SIGIO,
"IOT": unix.SIGIOT,
"KILL": unix.SIGKILL,
"PIPE": unix.SIGPIPE,
"POLL": unix.SIGPOLL,
"PROF": unix.SIGPROF,
"PWR": unix.SIGPWR,
"QUIT": unix.SIGQUIT,
"SEGV": unix.SIGSEGV,
"STKFLT": unix.SIGSTKFLT,
"STOP": unix.SIGSTOP,
"SYS": unix.SIGSYS,
"TERM": unix.SIGTERM,
"TRAP": unix.SIGTRAP,
"TSTP": unix.SIGTSTP,
"TTIN": unix.SIGTTIN,
"TTOU": unix.SIGTTOU,
"URG": unix.SIGURG,
"USR1": unix.SIGUSR1,
"USR2": unix.SIGUSR2,
"VTALRM": unix.SIGVTALRM,
"WINCH": unix.SIGWINCH,
"XCPU": unix.SIGXCPU,
"XFSZ": unix.SIGXFSZ,
"RTMIN": sigrtmin,
"RTMIN+1": sigrtmin + 1,
"RTMIN+2": sigrtmin + 2,
"RTMIN+3": sigrtmin + 3,
"RTMIN+4": sigrtmin + 4,
"RTMIN+5": sigrtmin + 5,
"RTMIN+6": sigrtmin + 6,
"RTMIN+7": sigrtmin + 7,
"RTMIN+8": sigrtmin + 8,
"RTMIN+9": sigrtmin + 9,
"RTMIN+10": sigrtmin + 10,
"RTMIN+11": sigrtmin + 11,
"RTMIN+12": sigrtmin + 12,
"RTMIN+13": sigrtmin + 13,
"RTMIN+14": sigrtmin + 14,
"RTMIN+15": sigrtmin + 15,
"RTMAX-14": sigrtmax - 14,
"RTMAX-13": sigrtmax - 13,
"RTMAX-12": sigrtmax - 12,
"RTMAX-11": sigrtmax - 11,
"RTMAX-10": sigrtmax - 10,
"RTMAX-9": sigrtmax - 9,
"RTMAX-8": sigrtmax - 8,
"RTMAX-7": sigrtmax - 7,
"RTMAX-6": sigrtmax - 6,
"RTMAX-5": sigrtmax - 5,
"RTMAX-4": sigrtmax - 4,
"RTMAX-3": sigrtmax - 3,
"RTMAX-2": sigrtmax - 2,
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}

View File

@ -0,0 +1,84 @@
// +build linux
// +build mips mipsle mips64 mips64le
package signal
import (
"syscall"
"golang.org/x/sys/unix"
)
const (
sigrtmin = 34
sigrtmax = 127
)
// SignalMap is a map of Linux signals.
var SignalMap = map[string]syscall.Signal{
"ABRT": unix.SIGABRT,
"ALRM": unix.SIGALRM,
"BUS": unix.SIGBUS,
"CHLD": unix.SIGCHLD,
"CLD": unix.SIGCLD,
"CONT": unix.SIGCONT,
"FPE": unix.SIGFPE,
"HUP": unix.SIGHUP,
"ILL": unix.SIGILL,
"INT": unix.SIGINT,
"IO": unix.SIGIO,
"IOT": unix.SIGIOT,
"KILL": unix.SIGKILL,
"PIPE": unix.SIGPIPE,
"POLL": unix.SIGPOLL,
"PROF": unix.SIGPROF,
"PWR": unix.SIGPWR,
"QUIT": unix.SIGQUIT,
"SEGV": unix.SIGSEGV,
"EMT": unix.SIGEMT,
"STOP": unix.SIGSTOP,
"SYS": unix.SIGSYS,
"TERM": unix.SIGTERM,
"TRAP": unix.SIGTRAP,
"TSTP": unix.SIGTSTP,
"TTIN": unix.SIGTTIN,
"TTOU": unix.SIGTTOU,
"URG": unix.SIGURG,
"USR1": unix.SIGUSR1,
"USR2": unix.SIGUSR2,
"VTALRM": unix.SIGVTALRM,
"WINCH": unix.SIGWINCH,
"XCPU": unix.SIGXCPU,
"XFSZ": unix.SIGXFSZ,
"RTMIN": sigrtmin,
"RTMIN+1": sigrtmin + 1,
"RTMIN+2": sigrtmin + 2,
"RTMIN+3": sigrtmin + 3,
"RTMIN+4": sigrtmin + 4,
"RTMIN+5": sigrtmin + 5,
"RTMIN+6": sigrtmin + 6,
"RTMIN+7": sigrtmin + 7,
"RTMIN+8": sigrtmin + 8,
"RTMIN+9": sigrtmin + 9,
"RTMIN+10": sigrtmin + 10,
"RTMIN+11": sigrtmin + 11,
"RTMIN+12": sigrtmin + 12,
"RTMIN+13": sigrtmin + 13,
"RTMIN+14": sigrtmin + 14,
"RTMIN+15": sigrtmin + 15,
"RTMAX-14": sigrtmax - 14,
"RTMAX-13": sigrtmax - 13,
"RTMAX-12": sigrtmax - 12,
"RTMAX-11": sigrtmax - 11,
"RTMAX-10": sigrtmax - 10,
"RTMAX-9": sigrtmax - 9,
"RTMAX-8": sigrtmax - 8,
"RTMAX-7": sigrtmax - 7,
"RTMAX-6": sigrtmax - 6,
"RTMAX-5": sigrtmax - 5,
"RTMAX-4": sigrtmax - 4,
"RTMAX-3": sigrtmax - 3,
"RTMAX-2": sigrtmax - 2,
"RTMAX-1": sigrtmax - 1,
"RTMAX": sigrtmax,
}

View File

@ -0,0 +1,34 @@
package sigproxy
import (
"context"
"os"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
"github.com/rootless-containers/rootlesskit/pkg/sigproxy/signal"
)
// ForwardAllSignals forwards signals.
// Based on https://github.com/docker/cli/blob/ef2f64abbd37edfa148f745fa0013731b5074d1b/cli/command/container/tty.go#L99-L126
func ForwardAllSignals(ctx context.Context, pid int) chan os.Signal {
sigc := make(chan os.Signal, 128)
signal.CatchAll(sigc)
go func() {
for s := range sigc {
if s == unix.SIGCHLD || s == unix.SIGPIPE || s == unix.SIGURG {
continue
}
us, ok := s.(unix.Signal)
if !ok {
logrus.Warnf("Unsupported signal %v", s)
continue
}
if err := unix.Kill(pid, us); err != nil {
logrus.WithError(err).Debugf("Error sending signal %v", s)
}
}
}()
return sigc
}

40
vendor/github.com/sirupsen/logrus/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,40 @@
run:
# do not run on test files yet
tests: false
# all available settings of specific linters
linters-settings:
errcheck:
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
# default is false: such cases aren't reported by default.
check-type-assertions: false
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
# default is false: such cases aren't reported by default.
check-blank: false
lll:
line-length: 100
tab-width: 4
prealloc:
simple: false
range-loops: false
for-loops: false
whitespace:
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
linters:
enable:
- megacheck
- govet
disable:
- maligned
- prealloc
disable-all: false
presets:
- bugs
- unused
fast: false

View File

@ -4,21 +4,13 @@ git:
depth: 1
env:
- GO111MODULE=on
- GO111MODULE=off
go: [ 1.11.x, 1.12.x ]
go: [1.13.x, 1.14.x]
os: [linux, osx]
matrix:
exclude:
- go: 1.12.x
env: GO111MODULE=off
- go: 1.11.x
os: osx
install:
- ./travis/install.sh
- if [[ "$GO111MODULE" == "on" ]]; then go mod download; fi
- if [[ "$GO111MODULE" == "off" ]]; then go get github.com/stretchr/testify/assert golang.org/x/sys/unix github.com/konsorten/go-windows-terminal-sequences; fi
script:
- ./travis/cross_build.sh
- ./travis/lint.sh
- export GOMAXPROCS=4
- export GORACE=halt_on_error=1
- go test -race -v ./...

View File

@ -1,3 +1,26 @@
# 1.6.0
Fixes:
* end of line cleanup
* revert the entry concurrency bug fix whic leads to deadlock under some circumstances
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
Features:
* add an option to the `TextFormatter` to completely disable fields quoting
# 1.5.0
Code quality:
* add golangci linter run on travis
Fixes:
* add mutex for hooks concurrent access on `Entry` data
* caller function field for go1.14
* fix build issue for gopherjs target
Feature:
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
* add a `DisableHTMLEscape` option in the `JSONFormatter`
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
# 1.4.2
* Fixes build break for plan9, nacl, solaris
# 1.4.1
@ -11,7 +34,7 @@ Fixes:
# 1.4.0
This new release introduces:
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter (#909, #911)
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
Fixes:

View File

@ -1,8 +1,28 @@
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus)&nbsp;[![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
Logrus is a structured logger for Go (golang), completely API compatible with
the standard library logger.
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
simply too hard to do in a way that won't break many people's projects, which is
the last thing you want from your Logging library (again...).
This does not mean Logrus is dead. Logrus will continue to be maintained for
security, (backwards compatible) bug fixes, and performance (where we are
limited by the interface).
I believe Logrus' biggest contribution is to have played a part in today's
widespread use of structured logging in Golang. There doesn't seem to be a
reason to do a major, breaking iteration into Logrus V2, since the fantastic Go
community has built those independently. Many fantastic alternatives have sprung
up. Logrus would look like those, had it been re-designed with what we know
about structured logging in Go today. Check out, for example,
[Zerolog][zerolog], [Zap][zap], and [Apex][apex].
[zerolog]: https://github.com/rs/zerolog
[zap]: https://github.com/uber-go/zap
[apex]: https://github.com/apex/log
**Seeing weird case-sensitive problems?** It's in the past been possible to
import Logrus as both upper- and lower-case. Due to the Go package environment,
this caused issues in the community and we needed a standard. Some environments
@ -15,11 +35,6 @@ comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
For an in-depth explanation of the casing issue, see [this
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
**Are you interested in assisting in maintaining Logrus?** Currently I have a
lot of obligations, and I am unable to provide Logrus with the maintainership it
needs. If you'd like to help, please reach out to me at `simon at author's
username dot com`.
Nicely color-coded in development (when a TTY is attached, otherwise just
plain text):
@ -187,7 +202,7 @@ func main() {
log.Out = os.Stdout
// You could set this to any `io.Writer` such as a file
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
// if err == nil {
// log.Out = file
// } else {
@ -272,7 +287,7 @@ func init() {
```
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
A list of currently known of service hook can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
#### Level logging
@ -354,6 +369,7 @@ The built-in logging formatters are:
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
* When colors are enabled, levels are truncated to 4 characters by default. To disable
truncation set the `DisableLevelTruncation` field to `true`.
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
* `logrus.JSONFormatter`. Logs fields as JSON.
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
@ -364,8 +380,10 @@ Third party logging formatters:
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo.
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files.
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
You can define your formatter by implementing the `Formatter` interface,
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
@ -430,14 +448,14 @@ entries. It should not be a feature of the application-level logger.
| Tool | Description |
| ---- | ----------- |
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.|
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
#### Testing
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
```go
@ -465,7 +483,7 @@ func TestSomething(t*testing.T){
Logrus can register one or more functions that will be called when any `fatal`
level message is logged. The registered handlers will be executed before
logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
```
@ -490,6 +508,6 @@ Situation when locking is not needed includes:
1) logger.Out is protected by locks.
2) logger.Out is a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)

View File

@ -85,10 +85,15 @@ func NewEntry(logger *Logger) *Entry {
}
}
// Returns the bytes representation of this entry from the formatter.
func (entry *Entry) Bytes() ([]byte, error) {
return entry.Logger.Formatter.Format(entry)
}
// Returns the string representation from the reader and ultimately the
// formatter.
func (entry *Entry) String() (string, error) {
serialized, err := entry.Logger.Formatter.Format(entry)
serialized, err := entry.Bytes()
if err != nil {
return "", err
}
@ -103,7 +108,11 @@ func (entry *Entry) WithError(err error) *Entry {
// Add a context to the Entry.
func (entry *Entry) WithContext(ctx context.Context) *Entry {
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: entry.Time, err: entry.err, Context: ctx}
dataCopy := make(Fields, len(entry.Data))
for k, v := range entry.Data {
dataCopy[k] = v
}
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
}
// Add a single field to the Entry.
@ -144,7 +153,11 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
// Overrides the time of the Entry.
func (entry *Entry) WithTime(t time.Time) *Entry {
return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err, Context: entry.Context}
dataCopy := make(Fields, len(entry.Data))
for k, v := range entry.Data {
dataCopy[k] = v
}
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
}
// getPackageName reduces a fully qualified function name to the package name
@ -165,15 +178,20 @@ func getPackageName(f string) string {
// getCaller retrieves the name of the first non-logrus calling function
func getCaller() *runtime.Frame {
// cache this package's fully-qualified name
callerInitOnce.Do(func() {
pcs := make([]uintptr, 2)
pcs := make([]uintptr, maximumCallerDepth)
_ = runtime.Callers(0, pcs)
logrusPackage = getPackageName(runtime.FuncForPC(pcs[1]).Name())
// now that we have the cache, we can skip a minimum count of known-logrus functions
// XXX this is dubious, the number of frames may vary
// dynamic get the package name and the minimum caller depth
for i := 0; i < maximumCallerDepth; i++ {
funcName := runtime.FuncForPC(pcs[i]).Name()
if strings.Contains(funcName, "getCaller") {
logrusPackage = getPackageName(funcName)
break
}
}
minimumCallerDepth = knownLogrusFrames
})
@ -187,7 +205,7 @@ func getCaller() *runtime.Frame {
// If the caller isn't part of this package, we're done
if pkg != logrusPackage {
return &f
return &f //nolint:scopelint
}
}
@ -217,9 +235,11 @@ func (entry Entry) log(level Level, msg string) {
entry.Level = level
entry.Message = msg
entry.Logger.mu.Lock()
if entry.Logger.ReportCaller {
entry.Caller = getCaller()
}
entry.Logger.mu.Unlock()
entry.fireHooks()
@ -255,11 +275,10 @@ func (entry *Entry) write() {
serialized, err := entry.Logger.Formatter.Format(entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
} else {
_, err = entry.Logger.Out.Write(serialized)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
return
}
if _, err = entry.Logger.Out.Write(serialized); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
}

View File

@ -80,7 +80,7 @@ func WithFields(fields Fields) *Entry {
return std.WithFields(fields)
}
// WithTime creats an entry from the standard logger and overrides the time of
// WithTime creates an entry from the standard logger and overrides the time of
// logs generated with it.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal

View File

@ -2,9 +2,10 @@ module github.com/sirupsen/logrus
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.1
github.com/konsorten/go-windows-terminal-sequences v1.0.3
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/testify v1.2.2
golang.org/x/sys v0.0.0-20190422165155-953cdadca894
)
go 1.13

View File

@ -1,16 +1,12 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -28,6 +28,9 @@ type JSONFormatter struct {
// DisableTimestamp allows disabling automatic timestamps in output
DisableTimestamp bool
// DisableHTMLEscape allows disabling html escaping in output
DisableHTMLEscape bool
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
DataKey string
@ -110,6 +113,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
}
encoder := json.NewEncoder(b)
encoder.SetEscapeHTML(!f.DisableHTMLEscape)
if f.PrettyPrint {
encoder.SetIndent("", " ")
}

View File

@ -68,10 +68,10 @@ func (mw *MutexWrap) Disable() {
// `Out` and `Hooks` directly on the default logger instance. You can also just
// instantiate your own:
//
// var log = &Logger{
// var log = &logrus.Logger{
// Out: os.Stderr,
// Formatter: new(JSONFormatter),
// Hooks: make(LevelHooks),
// Formatter: new(logrus.JSONFormatter),
// Hooks: make(logrus.LevelHooks),
// Level: logrus.DebugLevel,
// }
//
@ -100,8 +100,9 @@ func (logger *Logger) releaseEntry(entry *Entry) {
logger.entryPool.Put(entry)
}
// Adds a field to the log entry, note that it doesn't log until you call
// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry.
// WithField allocates a new entry and adds a field to it.
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
// this new returned entry.
// If you want multiple fields, use `WithFields`.
func (logger *Logger) WithField(key string, value interface{}) *Entry {
entry := logger.newEntry()

View File

@ -51,7 +51,7 @@ func (level *Level) UnmarshalText(text []byte) error {
return err
}
*level = Level(l)
*level = l
return nil
}

View File

@ -1,4 +1,5 @@
// +build darwin dragonfly freebsd netbsd openbsd
// +build !js
package logrus
@ -10,4 +11,3 @@ func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
return err == nil
}

View File

@ -0,0 +1,7 @@
// +build js
package logrus
func isTerminal(fd int) bool {
return false
}

View File

@ -1,4 +1,5 @@
// +build linux aix
// +build !js
package logrus
@ -10,4 +11,3 @@ func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
return err == nil
}

View File

@ -6,9 +6,11 @@ import (
"os"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
)
const (
@ -32,6 +34,14 @@ type TextFormatter struct {
// Force disabling colors.
DisableColors bool
// Force quoting of all values
ForceQuote bool
// DisableQuote disables quoting for all values.
// DisableQuote will have a lower priority than ForceQuote.
// If both of them are set to true, quote will be forced on all values.
DisableQuote bool
// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
EnvironmentOverrideColors bool
@ -57,6 +67,10 @@ type TextFormatter struct {
// Disables the truncation of the level text to 4 characters.
DisableLevelTruncation bool
// PadLevelText Adds padding the level text so that all the levels output at the same length
// PadLevelText is a superset of the DisableLevelTruncation option
PadLevelText bool
// QuoteEmptyFields will wrap empty fields in quotes if true
QuoteEmptyFields bool
@ -79,23 +93,32 @@ type TextFormatter struct {
CallerPrettyfier func(*runtime.Frame) (function string, file string)
terminalInitOnce sync.Once
// The max length of the level text, generated dynamically on init
levelTextMaxLength int
}
func (f *TextFormatter) init(entry *Entry) {
if entry.Logger != nil {
f.isTerminal = checkIfTerminal(entry.Logger.Out)
}
// Get the max length of the level text
for _, level := range AllLevels {
levelTextLength := utf8.RuneCount([]byte(level.String()))
if levelTextLength > f.levelTextMaxLength {
f.levelTextMaxLength = levelTextLength
}
}
}
func (f *TextFormatter) isColored() bool {
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
if f.EnvironmentOverrideColors {
if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); {
case ok && force != "0":
isColored = true
} else if ok && force == "0" {
isColored = false
} else if os.Getenv("CLICOLOR") == "0" {
case ok && force == "0", os.Getenv("CLICOLOR") == "0":
isColored = false
}
}
@ -217,9 +240,18 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
}
levelText := strings.ToUpper(entry.Level.String())
if !f.DisableLevelTruncation {
if !f.DisableLevelTruncation && !f.PadLevelText {
levelText = levelText[0:4]
}
if f.PadLevelText {
// Generates the format string used in the next line, for example "%-6s" or "%-7s".
// Based on the max level text length.
formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
// Formats the level text by appending spaces up to the max length, for example:
// - "INFO "
// - "WARNING"
levelText = fmt.Sprintf(formatString, levelText)
}
// Remove a single newline if it already exists in the message to keep
// the behavior of logrus text_formatter the same as the stdlib log package
@ -243,11 +275,12 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
}
}
if f.DisableTimestamp {
switch {
case f.DisableTimestamp:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
} else if !f.FullTimestamp {
case !f.FullTimestamp:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
} else {
default:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
}
for _, k := range keys {
@ -258,9 +291,15 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
}
func (f *TextFormatter) needsQuoting(text string) bool {
if f.ForceQuote {
return true
}
if f.QuoteEmptyFields && len(text) == 0 {
return true
}
if f.DisableQuote {
return false
}
for _, ch := range text {
if !((ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z') ||

View File

@ -6,10 +6,16 @@ import (
"runtime"
)
// Writer at INFO level. See WriterLevel for details.
func (logger *Logger) Writer() *io.PipeWriter {
return logger.WriterLevel(InfoLevel)
}
// WriterLevel returns an io.Writer that can be used to write arbitrary text to
// the logger at the given log level. Each line written to the writer will be
// printed in the usual way using formatters and hooks. The writer is part of an
// io.Pipe and it is the callers responsibility to close the writer when done.
// This can be used to override the standard library logger easily.
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
return NewEntry(logger).WriterLevel(level)
}

View File

@ -1,3 +1,7 @@
*.coverprofile
*.orig
node_modules/
vendor
.idea
internal/*/built-example
coverage.txt

View File

@ -1,36 +0,0 @@
language: go
sudo: false
dist: bionic
osx_image: xcode10
go:
- 1.11.x
- 1.12.x
- 1.13.x
os:
- linux
- osx
env:
GO111MODULE=on GOPROXY=https://proxy.golang.org
cache:
directories:
- node_modules
before_script:
- go get github.com/urfave/gfmrun/cmd/gfmrun
- go get golang.org/x/tools/cmd/goimports
- npm install markdown-toc
- go mod tidy
script:
- go run build.go vet
- go run build.go test
- go run build.go gfmrun docs/v1/manual.md
- go run build.go toc docs/v1/manual.md
- go run build.go gfmrun docs/v2/manual.md
- go run build.go toc docs/v2/manual.md
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -1,9 +1,6 @@
cli
===
[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli)
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli)
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
@ -20,12 +17,16 @@ Usage documentation exists for each major version. Don't know what version you'r
- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md)
- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md)
## Installation
Make sure you have a working Go environment. Go version 1.11+ is supported. [See the install instructions for Go](http://golang.org/doc/install.html).
Go Modules are strongly recommended when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules).
### Using `v2` releases
**Warning**: `v2` is in a beta state.
```
$ go get github.com/urfave/cli.v2
$ GO111MODULE=on go get github.com/urfave/cli/v2
```
```go
@ -39,7 +40,7 @@ import (
### Using `v1` releases
```
$ go get github.com/urfave/cli
$ GO111MODULE=on go get github.com/urfave/cli
```
```go
@ -50,11 +51,6 @@ import (
...
```
## Installation
Make sure you have a working Go environment. Go version 1.10+ is supported. [See
the install instructions for Go](http://golang.org/doc/install.html).
### GOPATH
Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can
@ -66,5 +62,5 @@ export PATH=$PATH:$GOPATH/bin
### Supported platforms
cli is tested against multiple versions of Go on Linux, and against the latest
released version of Go on OS X and Windows. For full details, see
[`./.travis.yml`](./.travis.yml) and [`./appveyor.yml`](./appveyor.yml).
released version of Go on OS X and Windows. This project uses Github Actions for
builds. For more build info, please look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/master/.github/workflows/cli.yml).

View File

@ -1,18 +1,18 @@
package cli
import (
"context"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"sort"
"time"
)
var (
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
changeLogURL = "https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
@ -43,8 +43,11 @@ type App struct {
Flags []Flag
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
// Boolean to hide built-in help command and help flag
HideHelp bool
// Boolean to hide built-in help command but keep help flag.
// Ignored if HideHelp is true.
HideHelpCommand bool
// Boolean to hide built-in version flag and the VERSION section of help
HideVersion bool
// categories contains the categorized commands and is populated on app startup
@ -110,7 +113,6 @@ func NewApp() *App {
HelpName: filepath.Base(os.Args[0]),
Usage: "A new cli application",
UsageText: "",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
@ -141,7 +143,7 @@ func (a *App) Setup() {
}
if a.Version == "" {
a.Version = "0.0.0"
a.HideVersion = true
}
if a.BashComplete == nil {
@ -171,7 +173,9 @@ func (a *App) Setup() {
a.Commands = newCommands
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
if !a.HideHelpCommand {
a.appendCommand(helpCommand)
}
if HelpFlag != nil {
a.appendFlag(HelpFlag)
@ -208,6 +212,13 @@ func (a *App) useShortOptionHandling() bool {
// Run is the entry point to the cli app. Parses the arguments slice and routes
// to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
return a.RunContext(context.Background(), arguments)
}
// RunContext is like Run except it takes a Context that will be
// passed to its commands and sub-commands. Through this, you can
// propagate timeouts and cancellation requests
func (a *App) RunContext(ctx context.Context, arguments []string) (err error) {
a.Setup()
// handle the completion flag separately from the flagset since
@ -223,9 +234,9 @@ func (a *App) Run(arguments []string) (err error) {
return err
}
err = parseIter(set, a, arguments[1:])
err = parseIter(set, a, arguments[1:], shellComplete)
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil)
context := NewContext(a, set, &Context{Context: ctx})
if nerr != nil {
_, _ = fmt.Fprintln(a.Writer, nerr)
_ = ShowAppHelp(context)
@ -322,19 +333,9 @@ func (a *App) RunAndExitOnError() {
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
// generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// Setup also handles HideHelp and HideHelpCommand
a.Setup()
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.appendCommand(helpCommand)
if HelpFlag != nil {
a.appendFlag(HelpFlag)
}
}
}
var newCmds []*Command
for _, c := range a.Commands {
if c.HelpName == "" {
@ -349,7 +350,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
return err
}
err = parseIter(set, a, ctx.Args().Tail())
err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete)
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)
@ -478,16 +479,6 @@ func (a *App) VisibleFlags() []Flag {
return visibleFlags(a.Flags)
}
func (a *App) hasFlag(flag Flag) bool {
for _, f := range a.Flags {
if reflect.DeepEqual(flag, f) {
return true
}
}
return false
}
func (a *App) errWriter() io.Writer {
// When the app ErrWriter is nil use the package level one.
if a.ErrWriter == nil {

View File

@ -1,29 +0,0 @@
version: "{build}"
os: Windows Server 2016
image: Visual Studio 2017
clone_folder: c:\gopath\src\github.com\urfave\cli
cache:
- node_modules
environment:
GOPATH: C:\gopath
GOVERSION: 1.11.x
GO111MODULE: on
GOPROXY: https://proxy.golang.org
install:
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
- go version
- go env
- go get github.com/urfave/gfmrun/cmd/gfmrun
- go get golang.org/x/tools/cmd/goimports
- go mod tidy
build_script:
- go run build.go vet
- go run build.go test
- go run build.go gfmrun docs/v1/manual.md

View File

@ -1,5 +1,6 @@
package cli
// CommandCategories interface allows for category manipulation
type CommandCategories interface {
// AddCommand adds a command to a category, creating a new category if necessary.
AddCommand(category string, command *Command)

View File

@ -41,8 +41,11 @@ type Command struct {
Flags []Flag
// Treat all flags as normal arguments if true
SkipFlagParsing bool
// Boolean to hide built-in help command
// Boolean to hide built-in help command and help flag
HideHelp bool
// Boolean to hide built-in help command but keep help flag
// Ignored if HideHelp is true.
HideHelpCommand bool
// Boolean to hide this command from help or completion
Hidden bool
// Boolean to enable short-option handling so user can combine several
@ -100,7 +103,7 @@ func (c *Command) Run(ctx *Context) (err error) {
c.UseShortOptionHandling = true
}
set, err := c.parseFlags(ctx.Args())
set, err := c.parseFlags(ctx.Args(), ctx.shellComplete)
context := NewContext(ctx.App, set, ctx)
context.Command = c
@ -174,7 +177,7 @@ func (c *Command) useShortOptionHandling() bool {
return c.UseShortOptionHandling
}
func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) {
set, err := c.newFlagSet()
if err != nil {
return nil, err
@ -184,7 +187,7 @@ func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
return set, set.Parse(append([]string{"--"}, args.Tail()...))
}
err = parseIter(set, c, args.Tail())
err = parseIter(set, c, args.Tail(), shellComplete)
if err != nil {
return nil, err
}
@ -236,6 +239,7 @@ func (c *Command) startApp(ctx *Context) error {
app.Commands = c.Subcommands
app.Flags = c.Flags
app.HideHelp = c.HideHelp
app.HideHelpCommand = c.HideHelpCommand
app.Version = ctx.App.Version
app.HideVersion = ctx.App.HideVersion

View File

@ -5,10 +5,7 @@ import (
"errors"
"flag"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
)
// Context is a type that is passed through to
@ -20,7 +17,6 @@ type Context struct {
App *App
Command *Command
shellComplete bool
setFlags map[string]bool
flagSet *flag.FlagSet
parentContext *Context
}
@ -31,19 +27,15 @@ func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
if parentCtx != nil {
c.Context = parentCtx.Context
c.shellComplete = parentCtx.shellComplete
if parentCtx.flagSet == nil {
parentCtx.flagSet = &flag.FlagSet{}
}
}
c.Command = &Command{}
if c.Context == nil {
ctx, cancel := context.WithCancel(context.Background())
go func() {
defer cancel()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
}()
c.Context = ctx
c.Context = context.Background()
}
return c
@ -114,8 +106,8 @@ func (c *Context) Lineage() []*Context {
return lineage
}
// value returns the value of the flag corresponding to `name`
func (c *Context) value(name string) interface{} {
// Value returns the value of the flag corresponding to `name`
func (c *Context) Value(name string) interface{} {
return c.flagSet.Lookup(name).Value.(flag.Getter).Get()
}

View File

@ -48,8 +48,8 @@ func (a *App) writeDocTemplate(w io.Writer) error {
return t.ExecuteTemplate(w, name, &cliTemplate{
App: a,
Commands: prepareCommands(a.Commands, 0),
GlobalArgs: prepareArgsWithValues(a.Flags),
SynopsisArgs: prepareArgsSynopsis(a.Flags),
GlobalArgs: prepareArgsWithValues(a.VisibleFlags()),
SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()),
})
}

View File

@ -48,6 +48,7 @@ func (m *multiError) Errors() []error {
return errs
}
// ErrorFormatter is the interface that will suitably format the error output
type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}

View File

@ -36,7 +36,7 @@ var VersionFlag Flag = &BoolFlag{
// HelpFlag prints the help for all commands and subcommands.
// Set to nil to disable the flag. The subcommand
// will still be added unless HideHelp is set to true.
// will still be added unless HideHelp or HideHelpCommand is set to true.
var HelpFlag Flag = &BoolFlag{
Name: "help",
Aliases: []string{"h"},
@ -203,12 +203,9 @@ func withEnvHint(envVars []string, str string) string {
return str + envText
}
func flagNames(f Flag) []string {
func flagNames(name string, aliases []string) []string {
var ret []string
name := flagStringField(f, "Name")
aliases := flagStringSliceField(f, "Aliases")
for _, part := range append([]string{name}, aliases...) {
// v1 -> v2 migration warning zone:
// Strip off anything after the first found comma or space, which
@ -231,17 +228,6 @@ func flagStringSliceField(f Flag, name string) []string {
return []string{}
}
func flagStringField(f Flag, name string) string {
fv := flagValue(f)
field := fv.FieldByName(name)
if field.IsValid() {
return field.String()
}
return ""
}
func withFileHint(filePath, str string) string {
fileText := ""
if filePath != "" {
@ -258,22 +244,26 @@ func flagValue(f Flag) reflect.Value {
return fv
}
func formatDefault(format string) string {
return " (default: " + format + ")"
}
func stringifyFlag(f Flag) string {
fv := flagValue(f)
switch f.(type) {
switch f := f.(type) {
case *IntSliceFlag:
return withEnvHint(flagStringSliceField(f, "EnvVars"),
stringifyIntSliceFlag(f.(*IntSliceFlag)))
stringifyIntSliceFlag(f))
case *Int64SliceFlag:
return withEnvHint(flagStringSliceField(f, "EnvVars"),
stringifyInt64SliceFlag(f.(*Int64SliceFlag)))
stringifyInt64SliceFlag(f))
case *Float64SliceFlag:
return withEnvHint(flagStringSliceField(f, "EnvVars"),
stringifyFloat64SliceFlag(f.(*Float64SliceFlag)))
stringifyFloat64SliceFlag(f))
case *StringSliceFlag:
return withEnvHint(flagStringSliceField(f, "EnvVars"),
stringifyStringSliceFlag(f.(*StringSliceFlag)))
stringifyStringSliceFlag(f))
}
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
@ -283,20 +273,20 @@ func stringifyFlag(f Flag) string {
val := fv.FieldByName("Value")
if val.IsValid() {
needsPlaceholder = val.Kind() != reflect.Bool
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
defaultValueString = fmt.Sprintf(formatDefault("%v"), val.Interface())
if val.Kind() == reflect.String && val.String() != "" {
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
defaultValueString = fmt.Sprintf(formatDefault("%q"), val.String())
}
}
helpText := fv.FieldByName("DefaultText")
if helpText.IsValid() && helpText.String() != "" {
needsPlaceholder = val.Kind() != reflect.Bool
defaultValueString = fmt.Sprintf(" (default: %s)", helpText.String())
defaultValueString = fmt.Sprintf(formatDefault("%s"), helpText.String())
}
if defaultValueString == " (default: )" {
if defaultValueString == formatDefault("") {
defaultValueString = ""
}
@ -365,7 +355,7 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string {
defaultVal := ""
if len(defaultVals) > 0 {
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
defaultVal = fmt.Sprintf(formatDefault("%s"), strings.Join(defaultVals, ", "))
}
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))

View File

@ -34,7 +34,7 @@ func (f *BoolFlag) String() string {
// Names returns the names of the flag
func (f *BoolFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required

View File

@ -34,7 +34,7 @@ func (f *DurationFlag) String() string {
// Names returns the names of the flag
func (f *DurationFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required

View File

@ -34,7 +34,7 @@ func (f *Float64Flag) String() string {
// Names returns the names of the flag
func (f *Float64Flag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required

View File

@ -90,7 +90,7 @@ func (f *Float64SliceFlag) String() string {
// Names returns the names of the flag
func (f *Float64SliceFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
@ -155,11 +155,9 @@ func (c *Context) Float64Slice(name string) []float64 {
func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Float64Slice)).Value(), error(nil)
if err != nil {
return nil
if slice, ok := f.Value.(*Float64Slice); ok {
return slice.Value()
}
return parsed
}
return nil
}

View File

@ -39,7 +39,7 @@ func (f *GenericFlag) String() string {
// Names returns the names of the flag
func (f *GenericFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required

View File

@ -34,7 +34,7 @@ func (f *IntFlag) String() string {
// Names returns the names of the flag
func (f *IntFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required

View File

@ -34,7 +34,7 @@ func (f *Int64Flag) String() string {
// Names returns the names of the flag
func (f *Int64Flag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required

View File

@ -91,7 +91,7 @@ func (f *Int64SliceFlag) String() string {
// Names returns the names of the flag
func (f *Int64SliceFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
@ -151,11 +151,9 @@ func (c *Context) Int64Slice(name string) []int64 {
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
if err != nil {
return nil
if slice, ok := f.Value.(*Int64Slice); ok {
return slice.Value()
}
return parsed
}
return nil
}

View File

@ -102,7 +102,7 @@ func (f *IntSliceFlag) String() string {
// Names returns the names of the flag
func (f *IntSliceFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
@ -165,11 +165,9 @@ func (c *Context) IntSlice(name string) []int {
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
if err != nil {
return nil
if slice, ok := f.Value.(*IntSlice); ok {
return slice.Value()
}
return parsed
}
return nil
}

View File

@ -30,7 +30,7 @@ func (f *PathFlag) String() string {
// Names returns the names of the flag
func (f *PathFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
@ -72,7 +72,7 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error {
return nil
}
// String looks up the value of a local PathFlag, returns
// Path looks up the value of a local PathFlag, returns
// "" if not found
func (c *Context) Path(name string) string {
if fs := lookupFlagSet(name, c); fs != nil {

View File

@ -31,7 +31,7 @@ func (f *StringFlag) String() string {
// Names returns the names of the flag
func (f *StringFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required

View File

@ -71,6 +71,7 @@ type StringSliceFlag struct {
Value *StringSlice
DefaultText string
HasBeenSet bool
Destination *StringSlice
}
// IsSet returns whether or not the flag has been set through env or file
@ -86,7 +87,7 @@ func (f *StringSliceFlag) String() string {
// Names returns the names of the flag
func (f *StringSliceFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
@ -117,13 +118,20 @@ func (f *StringSliceFlag) GetValue() string {
func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
f.Value = &StringSlice{}
destination := f.Value
if f.Destination != nil {
destination = f.Destination
}
for _, s := range strings.Split(val, ",") {
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
if err := destination.Set(strings.TrimSpace(s)); err != nil {
return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err)
}
}
// Set this to false so that we reset the slice if we then set values from
// flags that have already been set by the environment.
destination.hasBeenSet = false
f.HasBeenSet = true
}
@ -131,6 +139,12 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
if f.Value == nil {
f.Value = &StringSlice{}
}
if f.Destination != nil {
set.Var(f.Destination, name, f.Usage)
continue
}
set.Var(f.Value, name, f.Usage)
}
@ -149,11 +163,9 @@ func (c *Context) StringSlice(name string) []string {
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
if err != nil {
return nil
if slice, ok := f.Value.(*StringSlice); ok {
return slice.Value()
}
return parsed
}
return nil
}

152
vendor/github.com/urfave/cli/v2/flag_timestamp.go generated vendored Normal file
View File

@ -0,0 +1,152 @@
package cli
import (
"flag"
"fmt"
"time"
)
// Timestamp wrap to satisfy golang's flag interface.
type Timestamp struct {
timestamp *time.Time
hasBeenSet bool
layout string
}
// Timestamp constructor
func NewTimestamp(timestamp time.Time) *Timestamp {
return &Timestamp{timestamp: &timestamp}
}
// Set the timestamp value directly
func (t *Timestamp) SetTimestamp(value time.Time) {
if !t.hasBeenSet {
t.timestamp = &value
t.hasBeenSet = true
}
}
// Set the timestamp string layout for future parsing
func (t *Timestamp) SetLayout(layout string) {
t.layout = layout
}
// Parses the string value to timestamp
func (t *Timestamp) Set(value string) error {
timestamp, err := time.Parse(t.layout, value)
if err != nil {
return err
}
t.timestamp = &timestamp
t.hasBeenSet = true
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (t *Timestamp) String() string {
return fmt.Sprintf("%#v", t.timestamp)
}
// Value returns the timestamp value stored in the flag
func (t *Timestamp) Value() *time.Time {
return t.timestamp
}
// Get returns the flag structure
func (t *Timestamp) Get() interface{} {
return *t
}
// TimestampFlag is a flag with type time
type TimestampFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Layout string
Value *Timestamp
DefaultText string
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *TimestampFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *TimestampFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *TimestampFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *TimestampFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *TimestampFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *TimestampFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *TimestampFlag) GetValue() string {
if f.Value != nil {
return f.Value.timestamp.String()
}
return ""
}
// Apply populates the flag given the flag set and environment
func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
if f.Layout == "" {
return fmt.Errorf("timestamp Layout is required")
}
f.Value = &Timestamp{}
f.Value.SetLayout(f.Layout)
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if err := f.Value.Set(val); err != nil {
return fmt.Errorf("could not parse %q as timestamp value for flag %s: %s", val, f.Name, err)
}
f.HasBeenSet = true
}
for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage)
}
return nil
}
// Timestamp gets the timestamp from a flag name
func (c *Context) Timestamp(name string) *time.Time {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupTimestamp(name, fs)
}
return nil
}
// Fetches the timestamp value from the local timestampWrap
func lookupTimestamp(name string, set *flag.FlagSet) *time.Time {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*Timestamp)).Value()
}
return nil
}

View File

@ -34,7 +34,7 @@ func (f *UintFlag) String() string {
// Names returns the names of the flag
func (f *UintFlag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required

View File

@ -34,7 +34,7 @@ func (f *Uint64Flag) String() string {
// Names returns the names of the flag
func (f *Uint64Flag) Names() []string {
return flagNames(f)
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required

View File

@ -14,10 +14,15 @@ type iterativeParser interface {
// iteratively catch parsing errors. This way we achieve LR parsing without
// transforming any arguments. Otherwise, there is no way we can discriminate
// combined short options from common arguments that should be left untouched.
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string) error {
// Pass `shellComplete` to continue parsing options on failure during shell
// completion when, the user-supplied options may be incomplete.
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error {
for {
err := set.Parse(args)
if !ip.useShortOptionHandling() || err == nil {
if shellComplete {
return nil
}
return err
}

View File

@ -56,10 +56,13 @@ OPTIONS:
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
{{.HelpName}} - {{.Usage}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
@ -71,10 +74,7 @@ OPTIONS:
{{end}}{{end}}
`
var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }}
{{ range $Author := .App.Authors}}
% {{ $Author.Name }}
{{- end}}
var MarkdownDocTemplate = `% {{ .App.Name }} 8
# NAME

View File

@ -52,7 +52,6 @@ var isSpecialElementMap = map[string]bool{
"iframe": true,
"img": true,
"input": true,
"isindex": true, // The 'isindex' element has been removed, but keep it for backwards compatibility.
"keygen": true,
"li": true,
"link": true,

View File

@ -18,6 +18,11 @@ const (
ElementNode
CommentNode
DoctypeNode
// RawNode nodes are not returned by the parser, but can be part of the
// Node tree passed to func Render to insert raw HTML (without escaping).
// If so, this package makes no guarantee that the rendered HTML is secure
// (from e.g. Cross Site Scripting attacks) or well-formed.
RawNode
scopeMarkerNode
)

217
vendor/golang.org/x/net/html/parse.go generated vendored
View File

@ -184,6 +184,17 @@ func (p *parser) clearStackToContext(s scope) {
}
}
// parseGenericRawTextElements implements the generic raw text element parsing
// algorithm defined in 12.2.6.2.
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-elements-that-contain-only-text
// TODO: Since both RAWTEXT and RCDATA states are treated as tokenizer's part
// officially, need to make tokenizer consider both states.
func (p *parser) parseGenericRawTextElement() {
p.addElement()
p.originalIM = p.im
p.im = textIM
}
// generateImpliedEndTags pops nodes off the stack of open elements as long as
// the top node has a tag name of dd, dt, li, optgroup, option, p, rb, rp, rt or rtc.
// If exceptions are specified, nodes with that name will not be popped off.
@ -192,7 +203,9 @@ func (p *parser) generateImpliedEndTags(exceptions ...string) {
loop:
for i = len(p.oe) - 1; i >= 0; i-- {
n := p.oe[i]
if n.Type == ElementNode {
if n.Type != ElementNode {
break
}
switch n.DataAtom {
case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc:
for _, except := range exceptions {
@ -202,7 +215,6 @@ loop:
}
continue
}
}
break
}
@ -369,8 +381,7 @@ findIdenticalElements:
// Section 12.2.4.3.
func (p *parser) clearActiveFormattingElements() {
for {
n := p.afe.pop()
if len(p.afe) == 0 || n.Type == scopeMarkerNode {
if n := p.afe.pop(); len(p.afe) == 0 || n.Type == scopeMarkerNode {
return
}
}
@ -625,25 +636,29 @@ func inHeadIM(p *parser) bool {
switch p.tok.DataAtom {
case a.Html:
return inBodyIM(p)
case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta:
case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta:
p.addElement()
p.oe.pop()
p.acknowledgeSelfClosingTag()
return true
case a.Noscript:
p.addElement()
if p.scripting {
p.setOriginalIM()
p.im = textIM
} else {
p.im = inHeadNoscriptIM
}
p.parseGenericRawTextElement()
return true
case a.Script, a.Title, a.Noframes, a.Style:
}
p.addElement()
p.im = inHeadNoscriptIM
// Don't let the tokenizer go into raw text mode when scripting is disabled.
p.tokenizer.NextIsNotRawText()
return true
case a.Script, a.Title:
p.addElement()
p.setOriginalIM()
p.im = textIM
return true
case a.Noframes, a.Style:
p.parseGenericRawTextElement()
return true
case a.Head:
// Ignore the token.
return true
@ -855,7 +870,7 @@ func inBodyIM(p *parser) bool {
return true
}
copyAttributes(p.oe[0], p.tok)
case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title:
case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title:
return inHeadIM(p)
case a.Body:
if p.oe.contains(a.Template) {
@ -1014,53 +1029,6 @@ func inBodyIM(p *parser) bool {
p.tok.DataAtom = a.Img
p.tok.Data = a.Img.String()
return false
case a.Isindex:
if p.form != nil {
// Ignore the token.
return true
}
action := ""
prompt := "This is a searchable index. Enter search keywords: "
attr := []Attribute{{Key: "name", Val: "isindex"}}
for _, t := range p.tok.Attr {
switch t.Key {
case "action":
action = t.Val
case "name":
// Ignore the attribute.
case "prompt":
prompt = t.Val
default:
attr = append(attr, t)
}
}
p.acknowledgeSelfClosingTag()
p.popUntil(buttonScope, a.P)
p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
if p.form == nil {
// NOTE: The 'isindex' element has been removed,
// and the 'template' element has not been designed to be
// collaborative with the index element.
//
// Ignore the token.
return true
}
if action != "" {
p.form.Attr = []Attribute{{Key: "action", Val: action}}
}
p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
p.parseImpliedToken(StartTagToken, a.Label, a.Label.String())
p.addText(prompt)
p.addChild(&Node{
Type: ElementNode,
DataAtom: a.Input,
Data: a.Input.String(),
Attr: attr,
})
p.oe.pop()
p.parseImpliedToken(EndTagToken, a.Label, a.Label.String())
p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
p.parseImpliedToken(EndTagToken, a.Form, a.Form.String())
case a.Textarea:
p.addElement()
p.setOriginalIM()
@ -1070,18 +1038,21 @@ func inBodyIM(p *parser) bool {
p.popUntil(buttonScope, a.P)
p.reconstructActiveFormattingElements()
p.framesetOK = false
p.addElement()
p.setOriginalIM()
p.im = textIM
p.parseGenericRawTextElement()
case a.Iframe:
p.framesetOK = false
p.parseGenericRawTextElement()
case a.Noembed:
p.parseGenericRawTextElement()
case a.Noscript:
if p.scripting {
p.parseGenericRawTextElement()
return true
}
p.reconstructActiveFormattingElements()
p.addElement()
p.setOriginalIM()
p.im = textIM
case a.Noembed, a.Noscript:
p.addElement()
p.setOriginalIM()
p.im = textIM
// Don't let the tokenizer go into raw text mode when scripting is disabled.
p.tokenizer.NextIsNotRawText()
case a.Select:
p.reconstructActiveFormattingElements()
p.addElement()
@ -1198,7 +1169,7 @@ func inBodyIM(p *parser) bool {
if len(p.templateStack) > 0 {
p.im = inTemplateIM
return false
} else {
}
for _, e := range p.oe {
switch e.DataAtom {
case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc, a.Tbody, a.Td, a.Tfoot, a.Th,
@ -1208,7 +1179,6 @@ func inBodyIM(p *parser) bool {
}
}
}
}
return true
}
@ -1221,9 +1191,15 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom, tagName string) {
// Once the code successfully parses the comprehensive test suite, we should
// refactor this code to be more idiomatic.
// Steps 1-4. The outer loop.
// Steps 1-2
if current := p.oe.top(); current.Data == tagName && p.afe.index(current) == -1 {
p.oe.pop()
return
}
// Steps 3-5. The outer loop.
for i := 0; i < 8; i++ {
// Step 5. Find the formatting element.
// Step 6. Find the formatting element.
var formattingElement *Node
for j := len(p.afe) - 1; j >= 0; j-- {
if p.afe[j].Type == scopeMarkerNode {
@ -1238,17 +1214,22 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom, tagName string) {
p.inBodyEndTagOther(tagAtom, tagName)
return
}
// Step 7. Ignore the tag if formatting element is not in the stack of open elements.
feIndex := p.oe.index(formattingElement)
if feIndex == -1 {
p.afe.remove(formattingElement)
return
}
// Step 8. Ignore the tag if formatting element is not in the scope.
if !p.elementInScope(defaultScope, tagAtom) {
// Ignore the tag.
return
}
// Steps 9-10. Find the furthest block.
// Step 9. This step is omitted because it's just a parse error but no need to return.
// Steps 10-11. Find the furthest block.
var furthestBlock *Node
for _, e := range p.oe[feIndex:] {
if isSpecialElement(e) {
@ -1265,47 +1246,65 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom, tagName string) {
return
}
// Steps 11-12. Find the common ancestor and bookmark node.
// Steps 12-13. Find the common ancestor and bookmark node.
commonAncestor := p.oe[feIndex-1]
bookmark := p.afe.index(formattingElement)
// Step 13. The inner loop. Find the lastNode to reparent.
// Step 14. The inner loop. Find the lastNode to reparent.
lastNode := furthestBlock
node := furthestBlock
x := p.oe.index(node)
// Steps 13.1-13.2
for j := 0; j < 3; j++ {
// Step 13.3.
// Step 14.1.
j := 0
for {
// Step 14.2.
j++
// Step. 14.3.
x--
node = p.oe[x]
// Step 13.4 - 13.5.
// Step 14.4. Go to the next step if node is formatting element.
if node == formattingElement {
break
}
// Step 14.5. Remove node from the list of active formatting elements if
// inner loop counter is greater than three and node is in the list of
// active formatting elements.
if ni := p.afe.index(node); j > 3 && ni > -1 {
p.afe.remove(node)
// If any element of the list of active formatting elements is removed,
// we need to take care whether bookmark should be decremented or not.
// This is because the value of bookmark may exceed the size of the
// list by removing elements from the list.
if ni <= bookmark {
bookmark--
}
continue
}
// Step 14.6. Continue the next inner loop if node is not in the list of
// active formatting elements.
if p.afe.index(node) == -1 {
p.oe.remove(node)
continue
}
// Step 13.6.
if node == formattingElement {
break
}
// Step 13.7.
// Step 14.7.
clone := node.clone()
p.afe[p.afe.index(node)] = clone
p.oe[p.oe.index(node)] = clone
node = clone
// Step 13.8.
// Step 14.8.
if lastNode == furthestBlock {
bookmark = p.afe.index(node) + 1
}
// Step 13.9.
// Step 14.9.
if lastNode.Parent != nil {
lastNode.Parent.RemoveChild(lastNode)
}
node.AppendChild(lastNode)
// Step 13.10.
// Step 14.10.
lastNode = node
}
// Step 14. Reparent lastNode to the common ancestor,
// Step 15. Reparent lastNode to the common ancestor,
// or for misnested table nodes, to the foster parent.
if lastNode.Parent != nil {
lastNode.Parent.RemoveChild(lastNode)
@ -1317,13 +1316,13 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom, tagName string) {
commonAncestor.AppendChild(lastNode)
}
// Steps 15-17. Reparent nodes from the furthest block's children
// Steps 16-18. Reparent nodes from the furthest block's children
// to a clone of the formatting element.
clone := formattingElement.clone()
reparentChildren(clone, furthestBlock)
furthestBlock.AppendChild(clone)
// Step 18. Fix up the list of active formatting elements.
// Step 19. Fix up the list of active formatting elements.
if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark {
// Move the bookmark with the rest of the list.
bookmark--
@ -1331,7 +1330,7 @@ func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom, tagName string) {
p.afe.remove(formattingElement)
p.afe.insert(bookmark, clone)
// Step 19. Fix up the stack of open elements.
// Step 20. Fix up the stack of open elements.
p.oe.remove(formattingElement)
p.oe.insert(p.oe.index(furthestBlock)+1, clone)
}
@ -1502,14 +1501,13 @@ func inCaptionIM(p *parser) bool {
case StartTagToken:
switch p.tok.DataAtom {
case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Td, a.Tfoot, a.Thead, a.Tr:
if p.popUntil(tableScope, a.Caption) {
p.clearActiveFormattingElements()
p.im = inTableIM
return false
} else {
if !p.popUntil(tableScope, a.Caption) {
// Ignore the token.
return true
}
p.clearActiveFormattingElements()
p.im = inTableIM
return false
case a.Select:
p.reconstructActiveFormattingElements()
p.addElement()
@ -1526,14 +1524,13 @@ func inCaptionIM(p *parser) bool {
}
return true
case a.Table:
if p.popUntil(tableScope, a.Caption) {
p.clearActiveFormattingElements()
p.im = inTableIM
return false
} else {
if !p.popUntil(tableScope, a.Caption) {
// Ignore the token.
return true
}
p.clearActiveFormattingElements()
p.im = inTableIM
return false
case a.Body, a.Col, a.Colgroup, a.Html, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
// Ignore the token.
return true
@ -1777,12 +1774,11 @@ func inSelectIM(p *parser) bool {
}
p.addElement()
case a.Select:
if p.popUntil(selectScope, a.Select) {
p.resetInsertionMode()
} else {
if !p.popUntil(selectScope, a.Select) {
// Ignore the token.
return true
}
p.resetInsertionMode()
case a.Input, a.Keygen, a.Textarea:
if p.elementInScope(selectScope, a.Select) {
p.parseImpliedToken(EndTagToken, a.Select, a.Select.String())
@ -1810,12 +1806,11 @@ func inSelectIM(p *parser) bool {
p.oe = p.oe[:i]
}
case a.Select:
if p.popUntil(selectScope, a.Select) {
p.resetInsertionMode()
} else {
if !p.popUntil(selectScope, a.Select) {
// Ignore the token.
return true
}
p.resetInsertionMode()
case a.Template:
return inHeadIM(p)
}
@ -2352,8 +2347,7 @@ func ParseWithOptions(r io.Reader, opts ...ParseOption) (*Node, error) {
f(p)
}
err := p.parse()
if err != nil {
if err := p.parse(); err != nil {
return nil, err
}
return p.doc, nil
@ -2411,8 +2405,7 @@ func ParseFragmentWithOptions(r io.Reader, context *Node, opts ...ParseOption) (
}
}
err := p.parse()
if err != nil {
if err := p.parse(); err != nil {
return nil, err
}

View File

@ -134,6 +134,9 @@ func render1(w writer, n *Node) error {
}
}
return w.WriteByte('>')
case RawNode:
_, err := w.WriteString(n.Data)
return err
default:
return errors.New("html: unknown node type")
}
@ -256,7 +259,6 @@ var voidElements = map[string]bool{
"base": true,
"br": true,
"col": true,
"command": true,
"embed": true,
"hr": true,
"img": true,

View File

@ -296,8 +296,7 @@ func (z *Tokenizer) Buffered() []byte {
// too many times in succession.
func readAtLeastOneByte(r io.Reader, b []byte) (int, error) {
for i := 0; i < 100; i++ {
n, err := r.Read(b)
if n != 0 || err != nil {
if n, err := r.Read(b); n != 0 || err != nil {
return n, err
}
}

View File

@ -107,6 +107,7 @@ func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMis
// dialCall is an in-flight Transport dial call to a host.
type dialCall struct {
_ incomparable
p *clientConnPool
done chan struct{} // closed when done
res *ClientConn // valid after done is closed
@ -180,6 +181,7 @@ func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn)
}
type addConnCall struct {
_ incomparable
p *clientConnPool
done chan struct{} // closed when done
err error
@ -200,12 +202,6 @@ func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
close(c.done)
}
func (p *clientConnPool) addConn(key string, cc *ClientConn) {
p.mu.Lock()
p.addConnLocked(key, cc)
p.mu.Unlock()
}
// p.mu must be held
func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
for _, v := range p.conns[key] {

View File

@ -8,6 +8,8 @@ package http2
// flow is the flow control window's size.
type flow struct {
_ incomparable
// n is the number of DATA bytes we're allowed to send.
// A flow is kept both on a conn and a per-stream.
n int32

View File

@ -105,7 +105,14 @@ func huffmanDecode(buf *bytes.Buffer, maxLen int, v []byte) error {
return nil
}
// incomparable is a zero-width, non-comparable type. Adding it to a struct
// makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first).
type incomparable [0]func()
type node struct {
_ incomparable
// children is non-nil for internal nodes
children *[256]*node

View File

@ -19,7 +19,6 @@ package http2 // import "golang.org/x/net/http2"
import (
"bufio"
"crypto/tls"
"errors"
"fmt"
"io"
"net/http"
@ -173,11 +172,6 @@ func (s SettingID) String() string {
return fmt.Sprintf("UNKNOWN_SETTING_%d", uint16(s))
}
var (
errInvalidHeaderFieldName = errors.New("http2: invalid header field name")
errInvalidHeaderFieldValue = errors.New("http2: invalid header field value")
)
// validWireHeaderFieldName reports whether v is a valid header field
// name (key). See httpguts.ValidHeaderName for the base rules.
//
@ -247,6 +241,7 @@ func (cw closeWaiter) Wait() {
// Its buffered writer is lazily allocated as needed, to minimize
// idle memory usage with many connections.
type bufferedWriter struct {
_ incomparable
w io.Writer // immutable
bw *bufio.Writer // non-nil when data is buffered
}
@ -319,6 +314,7 @@ func bodyAllowedForStatus(status int) bool {
}
type httpError struct {
_ incomparable
msg string
timeout bool
}
@ -382,3 +378,8 @@ func (s *sorter) SortStrings(ss []string) {
func validPseudoPath(v string) bool {
return (len(v) > 0 && v[0] == '/') || v == "*"
}
// incomparable is a zero-width, non-comparable type. Adding it to a struct
// makes that struct also non-comparable, and generally doesn't add
// any size (as long as it's first).
type incomparable [0]func()

Some files were not shown because too many files have changed in this diff Show More