Merge pull request #20326 from hashicorp/vendor-grpc-go-bump
vendor: Bump google.golang.org/grpc & cloud.google.com/go & oauth2
This commit is contained in:
commit
4ab701620e
15
go.mod
15
go.mod
|
@ -1,7 +1,7 @@
|
|||
module github.com/hashicorp/terraform
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.15.0
|
||||
cloud.google.com/go v0.36.0
|
||||
github.com/Azure/azure-sdk-for-go v21.3.0+incompatible
|
||||
github.com/Azure/go-autorest v10.15.4+incompatible
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20170803034930-c92175d54006 // indirect
|
||||
|
@ -28,25 +28,20 @@ require (
|
|||
github.com/coreos/bbolt v1.3.0 // indirect
|
||||
github.com/coreos/etcd v3.3.10+incompatible
|
||||
github.com/coreos/go-semver v0.2.0 // indirect
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d // indirect
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31 // indirect
|
||||
github.com/dylanmei/iso8601 v0.1.0 // indirect
|
||||
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-test/deep v1.0.1
|
||||
github.com/gogo/protobuf v1.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 // indirect
|
||||
github.com/golang/mock v1.2.0
|
||||
github.com/golang/protobuf v1.2.0
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect
|
||||
github.com/google/go-cmp v0.2.0
|
||||
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e // indirect
|
||||
github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968
|
||||
github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01 // indirect
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f // indirect
|
||||
github.com/gorilla/websocket v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||
|
@ -125,16 +120,14 @@ require (
|
|||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
|
||||
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
||||
github.com/zclconf/go-cty v0.0.0-20190201220620-4ca19710f056
|
||||
go.opencensus.io v0.17.0 // indirect
|
||||
go.uber.org/atomic v1.3.2 // indirect
|
||||
go.uber.org/multierr v1.1.0 // indirect
|
||||
go.uber.org/zap v1.9.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd
|
||||
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced
|
||||
google.golang.org/api v0.0.0-20181015145326-625cd1887957
|
||||
google.golang.org/appengine v1.3.0 // indirect
|
||||
google.golang.org/grpc v1.14.0
|
||||
golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9
|
||||
google.golang.org/api v0.1.0
|
||||
google.golang.org/grpc v1.18.0
|
||||
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 // indirect
|
||||
labix.org/v2/mgo v0.0.0-20140701140051-000000000287 // indirect
|
||||
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect
|
||||
|
|
123
go.sum
123
go.sum
|
@ -1,5 +1,13 @@
|
|||
cloud.google.com/go v0.15.0 h1:/e2wXYguItvFu4fJCvhMRPIwwrimuUxI+aCVx/ahLjg=
|
||||
cloud.google.com/go v0.15.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.36.0 h1:+aCSj7tOo2LODWVEuZDZeGCckdt6MlSF+X/rB3wUiS8=
|
||||
cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/Azure/azure-sdk-for-go v21.3.0+incompatible h1:YFvAka2WKAl2xnJkYV1e1b7E2z88AgFszDzWU18ejMY=
|
||||
github.com/Azure/azure-sdk-for-go v21.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
|
@ -7,6 +15,7 @@ github.com/Azure/go-autorest v10.15.4+incompatible h1:q+DRrRdbCnkY7f2WxQBx58TwCG
|
|||
github.com/Azure/go-autorest v10.15.4+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20170803034930-c92175d54006 h1:dVyNL14dq1500JomYVzJTVi0XEcZFCYwwiNpDeCfoes=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20170803034930-c92175d54006/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290 h1:K9I21XUHNbYD3GNMmJBN0UKJCpdP+glftwNZ7Bo8kqY=
|
||||
github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
|
||||
github.com/Unknwon/com v0.0.0-20151008135407-28b053d5a292 h1:tuQ7w+my8a8mkwN7x2TSd7OzTjkZ7rAeSyH4xncuAMI=
|
||||
|
@ -17,6 +26,7 @@ github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tj
|
|||
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6 h1:LoeFxdq5zUCBQPhbQKE6zvoGwHMxCBlqwbH9+9kHoHA=
|
||||
github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd h1:S3Fr6QnkpW9VRjiEY4psQHhhbbahASuNVj52YIce7lI=
|
||||
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 h1:BFFG6KP8ASFBg2ptWsJn8p8RDufBjBDKIxLU7BTYGOM=
|
||||
|
@ -45,6 +55,7 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn
|
|||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/bsm/go-vlq v0.0.0-20150828105119-ec6e8d4f5f4e/go.mod h1:N+BjUcTjSxc2mtRGSCPsat1kze3CUtvJN3/jTXlp29k=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
|
@ -52,6 +63,7 @@ github.com/chzyer/readline v0.0.0-20161106042343-c914be64f07d h1:aG5FcWiZTOhPQzY
|
|||
github.com/chzyer/readline v0.0.0-20161106042343-c914be64f07d/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.0 h1:HIgH5xUWXT914HCI671AxuTTqjj64UOFr7pHn48LUTI=
|
||||
github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
|
||||
|
@ -71,21 +83,27 @@ github.com/dimchansky/utfbom v1.0.0 h1:fGC2kkf4qOoKqZ4q7iIh+Vef4ubC1c38UDsEyZynZ
|
|||
github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31 h1:Dzuw9GtbmllUqEcoHfScT9YpKFUssSiZ5PgZkIGf/YQ=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI=
|
||||
github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ=
|
||||
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08 h1:0bp6/GrNOrTDtSXe9YYGCwf8jp5Fb/b+4a6MTRm4qzY=
|
||||
github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08/go.mod h1:VBVDFSBXCIW8JaHQpI8lldSKfYaLMzP9oyq6IJ4fhzY=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
|
||||
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE=
|
||||
github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -97,22 +115,30 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCy
|
|||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e h1:CYRpN206UTHUinz3VJoLaBdy1gEGeJNsqT0mvswDcMw=
|
||||
github.com/googleapis/gax-go v0.0.0-20161107002406-da06d194a00e/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968 h1:Pu+HW4kcQozw0QyrTTgLE+3RXNqFhQNNzhbnoLFL83c=
|
||||
github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
|
||||
github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01 h1:OgCNGSnEalfkRpn//WGJHhpo7fkP+LhTpvEITZ7CkK4=
|
||||
github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f h1:JJ2EP5vV3LAD2U1CxQtD7PTOO15Y96kXmKDz7TjxGHs=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.1 h1:3scN4iuXkNOyP98jF55Lv8a9j1o/IwvnDIZ0LHJK1nk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hashicorp/aws-sdk-go-base v0.2.0 h1:5bjZnWCvQg9Im5CHZr9t90IaFC4uvVlMl2fTh23IoCk=
|
||||
|
@ -140,8 +166,6 @@ github.com/hashicorp/go-msgpack v0.0.0-20150518234257-fa3f63826f7c/go.mod h1:ahL
|
|||
github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-plugin v0.0.0-20190212232519-b838ffee39ce h1:I3KJUf8jyMubLFeHit2ibr9YeVxJX2CXMXVM6xlB+Qk=
|
||||
github.com/hashicorp/go-plugin v0.0.0-20190212232519-b838ffee39ce/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-plugin v0.0.0-20190220160451-3f118e8ee104 h1:9iQ/zrTOJqzP+kH37s6xNb6T1RysiT7fnDD3DJbspVw=
|
||||
github.com/hashicorp/go-plugin v0.0.0-20190220160451-3f118e8ee104/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.1 h1:Vsx5XKPqPs3M6sM4U4GWyUqFS8aBiL9U5gkgvpkg4SE=
|
||||
|
@ -182,6 +206,7 @@ github.com/hashicorp/vault v0.10.4/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bA
|
|||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
|
@ -195,11 +220,13 @@ github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/
|
|||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba h1:NARVGAAgEXvoMeNPHhPFt1SBt1VMznA3Gnz9d0qj+co=
|
||||
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
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/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=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
|
@ -222,6 +249,7 @@ github.com/mattn/go-shellwords v1.0.1 h1:2/mQs/EosKUge1MHnAavnrNwa0wLnWDjG4dTYMG
|
|||
github.com/mattn/go-shellwords v1.0.1/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.0.8 h1:Zi8HNpze3NeRWH1PQV6O71YcvJRQ6j0lORO6DAEmAAI=
|
||||
github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mitchellh/cli v0.0.0-20171129193617-33edc47170b5 h1:OYr3N2fY3e3kP/x/d81CJXlcZrIV2hH8gPnuRLpiME4=
|
||||
|
@ -249,6 +277,8 @@ github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784 h1:+DAetXqxv/
|
|||
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
|
@ -275,12 +305,35 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrO
|
|||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/satori/go.uuid v0.0.0-20160927100844-b061729afc07 h1:DEZDfcCVq3xDJrjqdCgyN/dHYVoqR92MCsdqCdxmnhM=
|
||||
github.com/satori/go.uuid v0.0.0-20160927100844-b061729afc07/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg=
|
||||
github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
|
@ -289,6 +342,8 @@ github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbm
|
|||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spf13/afero v1.0.2 h1:5bRmqmInNmNFkI9NG9O0Xc/Lgl9wOWWUUA/O8XZqTCo=
|
||||
github.com/spf13/afero v1.0.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
||||
|
@ -299,6 +354,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d h1:Z4EH+5EffvBEhh37F0C0DnpklTMh00JOkjW5zK3ofBI=
|
||||
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/terraform-providers/terraform-provider-openstack v1.15.0 h1:adpjqej+F8BAX9dHmuPF47sUIkgifeqBu6p7iCsyj0Y=
|
||||
github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=
|
||||
|
@ -321,64 +377,103 @@ github.com/zclconf/go-cty v0.0.0-20190124225737-a385d646c1e9 h1:hHCAGde+QfwbqXSA
|
|||
github.com/zclconf/go-cty v0.0.0-20190124225737-a385d646c1e9/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v0.0.0-20190201220620-4ca19710f056 h1:C6LhH3JHz2k6tnw5sYXBc8rD8SD/qFp6EhiZAcVyalk=
|
||||
github.com/zclconf/go-cty v0.0.0-20190201220620-4ca19710f056/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
go.opencensus.io v0.17.0 h1:2Cu88MYg+1LU+WVD+NWwYhyP0kKgRlN9QjWGaX0jKTE=
|
||||
go.opencensus.io v0.17.0/go.mod h1:mp1VrMQxhlqqDpKvH4UcQUa4YwlzNmymAjPrDdfxNpI=
|
||||
go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20180816225734-aabede6cba87/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76 h1:xx5MUFyRQRbPk6VjWjIE1epE/K5AoDD8QUN116NCy8k=
|
||||
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced h1:4oqSq7eft7MdPKBGQK11X9WYUxmj6ZLgGTqYIbY1kyw=
|
||||
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9 h1:pfyU+l9dEu0vZzDDMsdAKa1gZbJYEn6urYXj/+Xkz7s=
|
||||
golang.org/x/oauth2 v0.0.0-20190220154721-9b3c75971fc9/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc h1:WiYx1rIFmx8c0mXAFtv5D/mHyKe1+jmuP7PViuwqwuQ=
|
||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181015145326-625cd1887957 h1:jwCmWUTrTFfjsobRuGurnCQeW4NZKijaIf6yAXwLR0E=
|
||||
google.golang.org/api v0.0.0-20181015145326-625cd1887957/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922 h1:mBVYJnbrXLA/ZCBTCe7PtEgAUP+1bg92qTaFoPHdz+8=
|
||||
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
||||
google.golang.org/grpc v1.14.0 h1:ArxJuB1NWfPY6r9Gp9gqwplT0Ge7nqv9msgu03lHLmo=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA=
|
||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 h1:kb0VV7NuIojvRfzwslQeP3yArBqJHW9tOl4t38VS1jM=
|
||||
gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
labix.org/v2/mgo v0.0.0-20140701140051-000000000287 h1:L0cnkNl4TfAXzvdrqsYEmxOHOCv2p5I3taaReO8BWFs=
|
||||
labix.org/v2/mgo v0.0.0-20140701140051-000000000287/go.mod h1:Lg7AYkt1uXJoR9oeSZ3W/8IXLdvOfIITgZnommstyz4=
|
||||
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54=
|
||||
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
|
|
@ -22,10 +22,13 @@ David Symonds <dsymonds@golang.org>
|
|||
Filippo Valsorda <hi@filippo.io>
|
||||
Glenn Lewis <gmlewis@google.com>
|
||||
Ingo Oeser <nightlyone@googlemail.com>
|
||||
James Hall <james.hall@shopify.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Jonathan Amsterdam <jba@google.com>
|
||||
Kunpei Sakai <namusyaka@gmail.com>
|
||||
Luna Duclos <luna.duclos@palmstonegames.com>
|
||||
Magnus Hiie <magnus.hiie@gmail.com>
|
||||
Mario Castro <mariocaster@gmail.com>
|
||||
Michael McGreevy <mcgreevy@golang.org>
|
||||
Omar Jarjur <ojarjur@google.com>
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
|
|
|
@ -187,7 +187,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2014 Google Inc.
|
||||
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.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -20,6 +20,7 @@
|
|||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -31,9 +32,6 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -64,7 +62,7 @@ var (
|
|||
)
|
||||
|
||||
var (
|
||||
metaClient = &http.Client{
|
||||
defaultClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
|
@ -72,15 +70,15 @@ var (
|
|||
}).Dial,
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
},
|
||||
}
|
||||
subscribeClient = &http.Client{
|
||||
}}
|
||||
subscribeClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
},
|
||||
}
|
||||
}}
|
||||
)
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
|
@ -95,74 +93,16 @@ func (suffix NotDefinedError) Error() string {
|
|||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func Get(suffix string) (string, error) {
|
||||
val, _, err := getETag(metaClient, suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated
|
||||
// ETag using the provided client. This func is otherwise equivalent to Get.
|
||||
func getETag(client *http.Client, suffix string) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
url := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
func getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cachedValue) get() (v string, err error) {
|
||||
func (c *cachedValue) get(cl *Client) (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = getTrimmed(c.k)
|
||||
v, err = cl.getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = Get(c.k)
|
||||
v, err = cl.Get(c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
|
@ -197,11 +137,11 @@ func testOnGCE() bool {
|
|||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := ctxhttp.Do(ctx, metaClient, req)
|
||||
res, err := defaultClient.hc.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
|
@ -266,6 +206,255 @@ func systemInfoSuggestsGCE() bool {
|
|||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no
|
||||
// ResponseHeaderTimeout).
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return subscribeClient.Subscribe(suffix, fn)
|
||||
}
|
||||
|
||||
// Get calls Client.Get on the default client.
|
||||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return defaultClient.ProjectID() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) { return defaultClient.InternalIP() }
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) { return defaultClient.Hostname() }
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) { return defaultClient.InstanceID() }
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) { return defaultClient.InstanceName() }
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) { return defaultClient.Zone() }
|
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributes on the default client.
|
||||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
|
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributes on the default client.
|
||||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
|
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValue(attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValue(attr)
|
||||
}
|
||||
|
||||
// Scopes calls Client.Scopes on the default client.
|
||||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata. All HTTP requests
|
||||
// will use the given http.Client instead of the default client.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
return &Client{hc: c}
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(suffix string) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, _ := http.NewRequest("GET", u, nil)
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func (c *Client) Get(suffix string) (string, error) {
|
||||
val, _, err := c.getETag(suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *Client) getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = c.Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) lines(suffix string) ([]string, error) {
|
||||
j, err := c.Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) Hostname() (string, error) {
|
||||
return c.getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := c.Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceName() (string, error) {
|
||||
host, err := c.Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Split(host, ".")[0], nil
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) Zone() (string, error) {
|
||||
zone, err := c.getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
|
||||
return c.Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
|
||||
return c.Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
|
@ -275,11 +464,11 @@ func systemInfoSuggestsGCE() bool {
|
|||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := getETag(subscribeClient, suffix)
|
||||
val, lastETag, err := c.getETag(suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -295,7 +484,7 @@ func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
|||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag))
|
||||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
|
||||
if err != nil {
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
|
@ -311,127 +500,14 @@ func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
|||
}
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return projID.get() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return projNum.get() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) {
|
||||
return getTrimmed("instance/network-interfaces/0/ip")
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// Code is the HTTP response status code.
|
||||
Code int
|
||||
// Message is the server response message.
|
||||
Message string
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) {
|
||||
return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) {
|
||||
return getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) {
|
||||
return instID.get()
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) {
|
||||
host, err := Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Split(host, ".")[0], nil
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) {
|
||||
zone, err := getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }
|
||||
|
||||
func lines(suffix string) ([]string, error) {
|
||||
j, err := Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -22,9 +22,15 @@
|
|||
package iam
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
pb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// client abstracts the IAMPolicy API to allow multiple implementations.
|
||||
|
@ -39,26 +45,59 @@ type grpcClient struct {
|
|||
c pb.IAMPolicyClient
|
||||
}
|
||||
|
||||
var withRetry = gax.WithRetry(func() gax.Retryer {
|
||||
return gax.OnCodes([]codes.Code{
|
||||
codes.DeadlineExceeded,
|
||||
codes.Unavailable,
|
||||
}, gax.Backoff{
|
||||
Initial: 100 * time.Millisecond,
|
||||
Max: 60 * time.Second,
|
||||
Multiplier: 1.3,
|
||||
})
|
||||
})
|
||||
|
||||
func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) {
|
||||
proto, err := g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource})
|
||||
var proto *pb.Policy
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
var err error
|
||||
proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource})
|
||||
return err
|
||||
}, withRetry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proto, nil
|
||||
}
|
||||
|
||||
func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error {
|
||||
_, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{
|
||||
Resource: resource,
|
||||
Policy: p,
|
||||
})
|
||||
return err
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
_, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{
|
||||
Resource: resource,
|
||||
Policy: p,
|
||||
})
|
||||
return err
|
||||
}, withRetry)
|
||||
}
|
||||
|
||||
func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
|
||||
res, err := g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{
|
||||
Resource: resource,
|
||||
Permissions: perms,
|
||||
})
|
||||
var res *pb.TestIamPermissionsResponse
|
||||
md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
|
||||
ctx = insertMetadata(ctx, md)
|
||||
|
||||
err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
|
||||
var err error
|
||||
res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{
|
||||
Resource: resource,
|
||||
Permissions: perms,
|
||||
})
|
||||
return err
|
||||
}, withRetry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -76,7 +115,15 @@ type Handle struct {
|
|||
// InternalNewHandle returns a Handle for resource.
|
||||
// The conn parameter refers to a server that must support the IAMPolicy service.
|
||||
func InternalNewHandle(conn *grpc.ClientConn, resource string) *Handle {
|
||||
return InternalNewHandleClient(&grpcClient{c: pb.NewIAMPolicyClient(conn)}, resource)
|
||||
return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource)
|
||||
}
|
||||
|
||||
// InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// InternalNewHandleClient returns a Handle for resource using the given
|
||||
// grpc service that implements IAM as a mixin
|
||||
func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle {
|
||||
return InternalNewHandleClient(&grpcClient{c: c}, resource)
|
||||
}
|
||||
|
||||
// InternalNewHandleClient is for use by the Google Cloud Libraries only.
|
||||
|
@ -254,3 +301,15 @@ func memberIndex(m string, b *pb.Binding) int {
|
|||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// insertMetadata inserts metadata into the given context
|
||||
func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
|
||||
out, _ := metadata.FromOutgoingContext(ctx)
|
||||
out = out.Copy()
|
||||
for _, md := range mds {
|
||||
for k, v := range md {
|
||||
out[k] = append(out[k], v...)
|
||||
}
|
||||
}
|
||||
return metadata.NewOutgoingContext(ctx, out)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Annotate prepends msg to the error message in err, attempting
|
||||
// to preserve other information in err, like an error code.
|
||||
//
|
||||
// Annotate panics if err is nil.
|
||||
//
|
||||
// Annotate knows about these error types:
|
||||
// - "google.golang.org/grpc/status".Status
|
||||
// - "google.golang.org/api/googleapi".Error
|
||||
// If the error is not one of these types, Annotate behaves
|
||||
// like
|
||||
// fmt.Errorf("%s: %v", msg, err)
|
||||
func Annotate(err error, msg string) error {
|
||||
if err == nil {
|
||||
panic("Annotate called with nil")
|
||||
}
|
||||
if s, ok := status.FromError(err); ok {
|
||||
p := s.Proto()
|
||||
p.Message = msg + ": " + p.Message
|
||||
return status.ErrorProto(p)
|
||||
}
|
||||
if g, ok := err.(*googleapi.Error); ok {
|
||||
g.Message = msg + ": " + g.Message
|
||||
return g
|
||||
}
|
||||
return fmt.Errorf("%s: %v", msg, err)
|
||||
}
|
||||
|
||||
// Annotatef uses format and args to format a string, then calls Annotate.
|
||||
func Annotatef(err error, format string, args ...interface{}) error {
|
||||
return Annotate(err, fmt.Sprintf(format, args...))
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -15,12 +15,10 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
)
|
||||
|
||||
// Retry calls the supplied function f repeatedly according to the provided
|
||||
|
@ -48,7 +46,7 @@ func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error),
|
|||
p := bo.Pause()
|
||||
if cerr := sleep(ctx, p); cerr != nil {
|
||||
if lastErr != nil {
|
||||
return fmt.Errorf("%v; last function err: %v", cerr, lastErr)
|
||||
return Annotatef(lastErr, "retry failed with %v; last error", cerr)
|
||||
}
|
||||
return cerr
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opencensus.io/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/genproto/googleapis/rpc/code"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// StartSpan adds a span to the trace with the given name.
|
||||
func StartSpan(ctx context.Context, name string) context.Context {
|
||||
ctx, _ = trace.StartSpan(ctx, name)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// EndSpan ends a span with the given error.
|
||||
func EndSpan(ctx context.Context, err error) {
|
||||
span := trace.FromContext(ctx)
|
||||
if err != nil {
|
||||
span.SetStatus(toStatus(err))
|
||||
}
|
||||
span.End()
|
||||
}
|
||||
|
||||
// ToStatus interrogates an error and converts it to an appropriate
|
||||
// OpenCensus status.
|
||||
func toStatus(err error) trace.Status {
|
||||
if err2, ok := err.(*googleapi.Error); ok {
|
||||
return trace.Status{Code: httpStatusCodeToOCCode(err2.Code), Message: err2.Message}
|
||||
} else if s, ok := status.FromError(err); ok {
|
||||
return trace.Status{Code: int32(s.Code()), Message: s.Message()}
|
||||
} else {
|
||||
return trace.Status{Code: int32(code.Code_UNKNOWN), Message: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (deklerk): switch to using OpenCensus function when it becomes available.
|
||||
// Reference: https://github.com/googleapis/googleapis/blob/26b634d2724ac5dd30ae0b0cbfb01f07f2e4050e/google/rpc/code.proto
|
||||
func httpStatusCodeToOCCode(httpStatusCode int) int32 {
|
||||
switch httpStatusCode {
|
||||
case 200:
|
||||
return int32(code.Code_OK)
|
||||
case 499:
|
||||
return int32(code.Code_CANCELLED)
|
||||
case 500:
|
||||
return int32(code.Code_UNKNOWN) // Could also be Code_INTERNAL, Code_DATA_LOSS
|
||||
case 400:
|
||||
return int32(code.Code_INVALID_ARGUMENT) // Could also be Code_OUT_OF_RANGE
|
||||
case 504:
|
||||
return int32(code.Code_DEADLINE_EXCEEDED)
|
||||
case 404:
|
||||
return int32(code.Code_NOT_FOUND)
|
||||
case 409:
|
||||
return int32(code.Code_ALREADY_EXISTS) // Could also be Code_ABORTED
|
||||
case 403:
|
||||
return int32(code.Code_PERMISSION_DENIED)
|
||||
case 401:
|
||||
return int32(code.Code_UNAUTHENTICATED)
|
||||
case 429:
|
||||
return int32(code.Code_RESOURCE_EXHAUSTED)
|
||||
case 501:
|
||||
return int32(code.Code_UNIMPLEMENTED)
|
||||
case 503:
|
||||
return int32(code.Code_UNAVAILABLE)
|
||||
default:
|
||||
return int32(code.Code_UNKNOWN)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
// Repo is the current version of the client libraries in this
|
||||
// repo. It should be a date in YYYYMMDD format.
|
||||
const Repo = "20170928"
|
||||
const Repo = "20180226"
|
||||
|
||||
// Go returns the Go runtime version. The returned string
|
||||
// has no whitespace.
|
||||
|
@ -67,5 +67,5 @@ func goVer(s string) string {
|
|||
}
|
||||
|
||||
func notSemverRune(r rune) bool {
|
||||
return strings.IndexRune("0123456789.", r) < 0
|
||||
return !strings.ContainsRune("0123456789.", r)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -15,11 +15,11 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
@ -48,10 +48,21 @@ const (
|
|||
AllAuthenticatedUsers ACLEntity = "allAuthenticatedUsers"
|
||||
)
|
||||
|
||||
// ACLRule represents a grant for a role to an entity (user, group or team) for a Google Cloud Storage object or bucket.
|
||||
// ACLRule represents a grant for a role to an entity (user, group or team) for a
|
||||
// Google Cloud Storage object or bucket.
|
||||
type ACLRule struct {
|
||||
Entity ACLEntity
|
||||
Role ACLRole
|
||||
Entity ACLEntity
|
||||
EntityID string
|
||||
Role ACLRole
|
||||
Domain string
|
||||
Email string
|
||||
ProjectTeam *ProjectTeam
|
||||
}
|
||||
|
||||
// ProjectTeam is the project team associated with the entity, if any.
|
||||
type ProjectTeam struct {
|
||||
ProjectNumber string
|
||||
Team string
|
||||
}
|
||||
|
||||
// ACLHandle provides operations on an access control list for a Google Cloud Storage bucket or object.
|
||||
|
@ -64,7 +75,10 @@ type ACLHandle struct {
|
|||
}
|
||||
|
||||
// Delete permanently deletes the ACL entry for the given entity.
|
||||
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) error {
|
||||
func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Delete")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectDelete(ctx, entity)
|
||||
}
|
||||
|
@ -74,8 +88,11 @@ func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) error {
|
|||
return a.bucketDelete(ctx, entity)
|
||||
}
|
||||
|
||||
// Set sets the permission level for the given entity.
|
||||
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) error {
|
||||
// Set sets the role for the given entity.
|
||||
func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.Set")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectSet(ctx, entity, role, false)
|
||||
}
|
||||
|
@ -86,7 +103,10 @@ func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole) err
|
|||
}
|
||||
|
||||
// List retrieves ACL entries.
|
||||
func (a *ACLHandle) List(ctx context.Context) ([]ACLRule, error) {
|
||||
func (a *ACLHandle) List(ctx context.Context) (rules []ACLRule, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.ACL.List")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if a.object != "" {
|
||||
return a.objectList(ctx)
|
||||
}
|
||||
|
@ -101,26 +121,22 @@ func (a *ACLHandle) bucketDefaultList(ctx context.Context) ([]ACLRule, error) {
|
|||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.DefaultObjectAccessControls.List(a.bucket)
|
||||
a.configureCall(req, ctx)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: error listing default object ACL for bucket %q: %v", a.bucket, err)
|
||||
return nil, err
|
||||
}
|
||||
return toACLRules(acls.Items), nil
|
||||
return toObjectACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDefaultDelete(ctx context.Context, entity ACLEntity) error {
|
||||
err := runWithRetry(ctx, func() error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.DefaultObjectAccessControls.Delete(a.bucket, string(entity))
|
||||
a.configureCall(req, ctx)
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error deleting default ACL entry for bucket %q, entity %q: %v", a.bucket, entity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketList(ctx context.Context) ([]ACLRule, error) {
|
||||
|
@ -128,19 +144,14 @@ func (a *ACLHandle) bucketList(ctx context.Context) ([]ACLRule, error) {
|
|||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.List(a.bucket)
|
||||
a.configureCall(req, ctx)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: error listing bucket ACL for bucket %q: %v", a.bucket, err)
|
||||
return nil, err
|
||||
}
|
||||
r := make([]ACLRule, len(acls.Items))
|
||||
for i, v := range acls.Items {
|
||||
r[i].Entity = ACLEntity(v.Entity)
|
||||
r[i].Role = ACLRole(v.Role)
|
||||
}
|
||||
return r, nil
|
||||
return toBucketACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketSet(ctx context.Context, entity ACLEntity, role ACLRole) error {
|
||||
|
@ -151,26 +162,22 @@ func (a *ACLHandle) bucketSet(ctx context.Context, entity ACLEntity, role ACLRol
|
|||
}
|
||||
err := runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.Update(a.bucket, string(entity), acl)
|
||||
a.configureCall(req, ctx)
|
||||
a.configureCall(ctx, req)
|
||||
_, err := req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error updating bucket ACL entry for bucket %q, entity %q: %v", a.bucket, entity, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) bucketDelete(ctx context.Context, entity ACLEntity) error {
|
||||
err := runWithRetry(ctx, func() error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.BucketAccessControls.Delete(a.bucket, string(entity))
|
||||
a.configureCall(req, ctx)
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error deleting bucket ACL entry for bucket %q, entity %q: %v", a.bucket, entity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectList(ctx context.Context) ([]ACLRule, error) {
|
||||
|
@ -178,14 +185,14 @@ func (a *ACLHandle) objectList(ctx context.Context) ([]ACLRule, error) {
|
|||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.ObjectAccessControls.List(a.bucket, a.object)
|
||||
a.configureCall(req, ctx)
|
||||
a.configureCall(ctx, req)
|
||||
acls, err = req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: error listing object ACL for bucket %q, file %q: %v", a.bucket, a.object, err)
|
||||
return nil, err
|
||||
}
|
||||
return toACLRules(acls.Items), nil
|
||||
return toObjectACLRules(acls.Items), nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectSet(ctx context.Context, entity ACLEntity, role ACLRole, isBucketDefault bool) error {
|
||||
|
@ -205,36 +212,22 @@ func (a *ACLHandle) objectSet(ctx context.Context, entity ACLEntity, role ACLRol
|
|||
} else {
|
||||
req = a.c.raw.ObjectAccessControls.Update(a.bucket, a.object, string(entity), acl)
|
||||
}
|
||||
a.configureCall(req, ctx)
|
||||
err := runWithRetry(ctx, func() error {
|
||||
a.configureCall(ctx, req)
|
||||
return runWithRetry(ctx, func() error {
|
||||
_, err := req.Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
if isBucketDefault {
|
||||
return fmt.Errorf("storage: error updating default ACL entry for bucket %q, entity %q: %v", a.bucket, entity, err)
|
||||
} else {
|
||||
return fmt.Errorf("storage: error updating object ACL entry for bucket %q, object %q, entity %q: %v", a.bucket, a.object, entity, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) objectDelete(ctx context.Context, entity ACLEntity) error {
|
||||
err := runWithRetry(ctx, func() error {
|
||||
return runWithRetry(ctx, func() error {
|
||||
req := a.c.raw.ObjectAccessControls.Delete(a.bucket, a.object, string(entity))
|
||||
a.configureCall(req, ctx)
|
||||
a.configureCall(ctx, req)
|
||||
return req.Do()
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("storage: error deleting object ACL entry for bucket %q, file %q, entity %q: %v", a.bucket, a.object, entity, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ACLHandle) configureCall(call interface {
|
||||
Header() http.Header
|
||||
}, ctx context.Context) {
|
||||
func (a *ACLHandle) configureCall(ctx context.Context, call interface{ Header() http.Header }) {
|
||||
vc := reflect.ValueOf(call)
|
||||
vc.MethodByName("Context").Call([]reflect.Value{reflect.ValueOf(ctx)})
|
||||
if a.userProject != "" {
|
||||
|
@ -243,10 +236,100 @@ func (a *ACLHandle) configureCall(call interface {
|
|||
setClientHeader(call.Header())
|
||||
}
|
||||
|
||||
func toACLRules(items []*raw.ObjectAccessControl) []ACLRule {
|
||||
r := make([]ACLRule, 0, len(items))
|
||||
func toObjectACLRules(items []*raw.ObjectAccessControl) []ACLRule {
|
||||
var rs []ACLRule
|
||||
for _, item := range items {
|
||||
r = append(r, ACLRule{Entity: ACLEntity(item.Entity), Role: ACLRole(item.Role)})
|
||||
rs = append(rs, toObjectACLRule(item))
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func toBucketACLRules(items []*raw.BucketAccessControl) []ACLRule {
|
||||
var rs []ACLRule
|
||||
for _, item := range items {
|
||||
rs = append(rs, toBucketACLRule(item))
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func toObjectACLRule(a *raw.ObjectAccessControl) ACLRule {
|
||||
return ACLRule{
|
||||
Entity: ACLEntity(a.Entity),
|
||||
EntityID: a.EntityId,
|
||||
Role: ACLRole(a.Role),
|
||||
Domain: a.Domain,
|
||||
Email: a.Email,
|
||||
ProjectTeam: toObjectProjectTeam(a.ProjectTeam),
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketACLRule(a *raw.BucketAccessControl) ACLRule {
|
||||
return ACLRule{
|
||||
Entity: ACLEntity(a.Entity),
|
||||
EntityID: a.EntityId,
|
||||
Role: ACLRole(a.Role),
|
||||
Domain: a.Domain,
|
||||
Email: a.Email,
|
||||
ProjectTeam: toBucketProjectTeam(a.ProjectTeam),
|
||||
}
|
||||
}
|
||||
|
||||
func toRawObjectACL(rules []ACLRule) []*raw.ObjectAccessControl {
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]*raw.ObjectAccessControl, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
r = append(r, rule.toRawObjectAccessControl("")) // bucket name unnecessary
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func toRawBucketACL(rules []ACLRule) []*raw.BucketAccessControl {
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
r := make([]*raw.BucketAccessControl, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
r = append(r, rule.toRawBucketAccessControl("")) // bucket name unnecessary
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r ACLRule) toRawBucketAccessControl(bucket string) *raw.BucketAccessControl {
|
||||
return &raw.BucketAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(r.Entity),
|
||||
Role: string(r.Role),
|
||||
// The other fields are not settable.
|
||||
}
|
||||
}
|
||||
|
||||
func (r ACLRule) toRawObjectAccessControl(bucket string) *raw.ObjectAccessControl {
|
||||
return &raw.ObjectAccessControl{
|
||||
Bucket: bucket,
|
||||
Entity: string(r.Entity),
|
||||
Role: string(r.Role),
|
||||
// The other fields are not settable.
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketProjectTeam(p *raw.BucketAccessControlProjectTeam) *ProjectTeam {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &ProjectTeam{
|
||||
ProjectNumber: p.ProjectNumber,
|
||||
Team: p.Team,
|
||||
}
|
||||
}
|
||||
|
||||
func toObjectProjectTeam(p *raw.ObjectAccessControlProjectTeam) *ProjectTeam {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return &ProjectTeam{
|
||||
ProjectNumber: p.ProjectNumber,
|
||||
Team: p.Team,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 Google Inc. LiveAndArchived Rights Reserved.
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -15,13 +15,14 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/optional"
|
||||
"golang.org/x/net/context"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/iterator"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
|
@ -35,7 +36,7 @@ type BucketHandle struct {
|
|||
acl ACLHandle
|
||||
defaultObjectACL ACLHandle
|
||||
conds *BucketConditions
|
||||
userProject string // project for requester-pays buckets
|
||||
userProject string // project for Requester Pays buckets
|
||||
}
|
||||
|
||||
// Bucket returns a BucketHandle, which provides operations on the named bucket.
|
||||
|
@ -63,7 +64,10 @@ func (c *Client) Bucket(name string) *BucketHandle {
|
|||
|
||||
// Create creates the Bucket in the project.
|
||||
// If attrs is nil the API defaults will be used.
|
||||
func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *BucketAttrs) error {
|
||||
func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *BucketAttrs) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Create")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
var bkt *raw.Bucket
|
||||
if attrs != nil {
|
||||
bkt = attrs.toRawBucket()
|
||||
|
@ -78,11 +82,20 @@ func (b *BucketHandle) Create(ctx context.Context, projectID string, attrs *Buck
|
|||
}
|
||||
req := b.c.raw.Buckets.Insert(projectID, bkt)
|
||||
setClientHeader(req.Header())
|
||||
if attrs != nil && attrs.PredefinedACL != "" {
|
||||
req.PredefinedAcl(attrs.PredefinedACL)
|
||||
}
|
||||
if attrs != nil && attrs.PredefinedDefaultObjectACL != "" {
|
||||
req.PredefinedDefaultObjectAcl(attrs.PredefinedDefaultObjectACL)
|
||||
}
|
||||
return runWithRetry(ctx, func() error { _, err := req.Context(ctx).Do(); return err })
|
||||
}
|
||||
|
||||
// Delete deletes the Bucket.
|
||||
func (b *BucketHandle) Delete(ctx context.Context) error {
|
||||
func (b *BucketHandle) Delete(ctx context.Context) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Delete")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
req, err := b.newDeleteCall()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -139,7 +152,10 @@ func (b *BucketHandle) Object(name string) *ObjectHandle {
|
|||
}
|
||||
|
||||
// Attrs returns the metadata for the bucket.
|
||||
func (b *BucketHandle) Attrs(ctx context.Context) (*BucketAttrs, error) {
|
||||
func (b *BucketHandle) Attrs(ctx context.Context) (attrs *BucketAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Attrs")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
req, err := b.newGetCall()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -155,7 +171,7 @@ func (b *BucketHandle) Attrs(ctx context.Context) (*BucketAttrs, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newBucket(resp), nil
|
||||
return newBucket(resp)
|
||||
}
|
||||
|
||||
func (b *BucketHandle) newGetCall() (*raw.BucketsGetCall, error) {
|
||||
|
@ -170,17 +186,27 @@ func (b *BucketHandle) newGetCall() (*raw.BucketsGetCall, error) {
|
|||
return req, nil
|
||||
}
|
||||
|
||||
func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate) (*BucketAttrs, error) {
|
||||
// Update updates a bucket's attributes.
|
||||
func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate) (attrs *BucketAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Create")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
req, err := b.newPatchCall(&uattrs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if uattrs.PredefinedACL != "" {
|
||||
req.PredefinedAcl(uattrs.PredefinedACL)
|
||||
}
|
||||
if uattrs.PredefinedDefaultObjectACL != "" {
|
||||
req.PredefinedDefaultObjectAcl(uattrs.PredefinedDefaultObjectACL)
|
||||
}
|
||||
// TODO(jba): retry iff metagen is set?
|
||||
rb, err := req.Context(ctx).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newBucket(rb), nil
|
||||
return newBucket(rb)
|
||||
}
|
||||
|
||||
func (b *BucketHandle) newPatchCall(uattrs *BucketAttrsToUpdate) (*raw.BucketsPatchCall, error) {
|
||||
|
@ -206,10 +232,32 @@ type BucketAttrs struct {
|
|||
// ACL is the list of access control rules on the bucket.
|
||||
ACL []ACLRule
|
||||
|
||||
// BucketPolicyOnly configures access checks to use only bucket-level IAM
|
||||
// policies.
|
||||
BucketPolicyOnly BucketPolicyOnly
|
||||
|
||||
// DefaultObjectACL is the list of access controls to
|
||||
// apply to new objects when no object ACL is provided.
|
||||
DefaultObjectACL []ACLRule
|
||||
|
||||
// DefaultEventBasedHold is the default value for event-based hold on
|
||||
// newly created objects in this bucket. It defaults to false.
|
||||
DefaultEventBasedHold bool
|
||||
|
||||
// If not empty, applies a predefined set of access controls. It should be set
|
||||
// only when creating a bucket.
|
||||
// It is always empty for BucketAttrs returned from the service.
|
||||
// See https://cloud.google.com/storage/docs/json_api/v1/buckets/insert
|
||||
// for valid values.
|
||||
PredefinedACL string
|
||||
|
||||
// If not empty, applies a predefined set of default object access controls.
|
||||
// It should be set only when creating a bucket.
|
||||
// It is always empty for BucketAttrs returned from the service.
|
||||
// See https://cloud.google.com/storage/docs/json_api/v1/buckets/insert
|
||||
// for valid values.
|
||||
PredefinedDefaultObjectACL string
|
||||
|
||||
// Location is the location of the bucket. It defaults to "US".
|
||||
Location string
|
||||
|
||||
|
@ -237,9 +285,45 @@ type BucketAttrs struct {
|
|||
Labels map[string]string
|
||||
|
||||
// RequesterPays reports whether the bucket is a Requester Pays bucket.
|
||||
// Clients performing operations on Requester Pays buckets must provide
|
||||
// a user project (see BucketHandle.UserProject), which will be billed
|
||||
// for the operations.
|
||||
RequesterPays bool
|
||||
|
||||
// Lifecycle is the lifecycle configuration for objects in the bucket.
|
||||
Lifecycle Lifecycle
|
||||
|
||||
// Retention policy enforces a minimum retention time for all objects
|
||||
// contained in the bucket. A RetentionPolicy of nil implies the bucket
|
||||
// has no minimum data retention.
|
||||
//
|
||||
// This feature is in private alpha release. It is not currently available to
|
||||
// most customers. It might be changed in backwards-incompatible ways and is not
|
||||
// subject to any SLA or deprecation policy.
|
||||
RetentionPolicy *RetentionPolicy
|
||||
|
||||
// The bucket's Cross-Origin Resource Sharing (CORS) configuration.
|
||||
CORS []CORS
|
||||
|
||||
// The encryption configuration used by default for newly inserted objects.
|
||||
Encryption *BucketEncryption
|
||||
|
||||
// The logging configuration.
|
||||
Logging *BucketLogging
|
||||
|
||||
// The website configuration.
|
||||
Website *BucketWebsite
|
||||
}
|
||||
|
||||
// BucketPolicyOnly configures access checks to use only bucket-level IAM
|
||||
// policies.
|
||||
type BucketPolicyOnly struct {
|
||||
// Enabled specifies whether access checks use only bucket-level IAM
|
||||
// policies. Enabled may be disabled until the locked time.
|
||||
Enabled bool
|
||||
// LockedTime specifies the deadline for changing Enabled from true to
|
||||
// false.
|
||||
LockedTime time.Time
|
||||
}
|
||||
|
||||
// Lifecycle is the lifecycle configuration for objects in the bucket.
|
||||
|
@ -247,12 +331,42 @@ type Lifecycle struct {
|
|||
Rules []LifecycleRule
|
||||
}
|
||||
|
||||
// RetentionPolicy enforces a minimum retention time for all objects
|
||||
// contained in the bucket.
|
||||
//
|
||||
// Any attempt to overwrite or delete objects younger than the retention
|
||||
// period will result in an error. An unlocked retention policy can be
|
||||
// modified or removed from the bucket via the Update method. A
|
||||
// locked retention policy cannot be removed or shortened in duration
|
||||
// for the lifetime of the bucket.
|
||||
//
|
||||
// This feature is in private alpha release. It is not currently available to
|
||||
// most customers. It might be changed in backwards-incompatible ways and is not
|
||||
// subject to any SLA or deprecation policy.
|
||||
type RetentionPolicy struct {
|
||||
// RetentionPeriod specifies the duration that objects need to be
|
||||
// retained. Retention duration must be greater than zero and less than
|
||||
// 100 years. Note that enforcement of retention periods less than a day
|
||||
// is not guaranteed. Such periods should only be used for testing
|
||||
// purposes.
|
||||
RetentionPeriod time.Duration
|
||||
|
||||
// EffectiveTime is the time from which the policy was enforced and
|
||||
// effective. This field is read-only.
|
||||
EffectiveTime time.Time
|
||||
|
||||
// IsLocked describes whether the bucket is locked. Once locked, an
|
||||
// object retention policy cannot be modified.
|
||||
// This field is read-only.
|
||||
IsLocked bool
|
||||
}
|
||||
|
||||
const (
|
||||
// RFC3339 date with only the date segment, used for CreatedBefore in LifecycleRule.
|
||||
rfc3339Date = "2006-01-02"
|
||||
|
||||
// DeleteAction is a lifecycle action that deletes a live and/or archived
|
||||
// objects. Takes precendence over SetStorageClass actions.
|
||||
// objects. Takes precedence over SetStorageClass actions.
|
||||
DeleteAction = "Delete"
|
||||
|
||||
// SetStorageClassAction changes the storage class of live and/or archived
|
||||
|
@ -332,53 +446,66 @@ type LifecycleCondition struct {
|
|||
NumNewerVersions int64
|
||||
}
|
||||
|
||||
func newBucket(b *raw.Bucket) *BucketAttrs {
|
||||
// BucketLogging holds the bucket's logging configuration, which defines the
|
||||
// destination bucket and optional name prefix for the current bucket's
|
||||
// logs.
|
||||
type BucketLogging struct {
|
||||
// The destination bucket where the current bucket's logs
|
||||
// should be placed.
|
||||
LogBucket string
|
||||
|
||||
// A prefix for log object names.
|
||||
LogObjectPrefix string
|
||||
}
|
||||
|
||||
// BucketWebsite holds the bucket's website configuration, controlling how the
|
||||
// service behaves when accessing bucket contents as a web site. See
|
||||
// https://cloud.google.com/storage/docs/static-website for more information.
|
||||
type BucketWebsite struct {
|
||||
// If the requested object path is missing, the service will ensure the path has
|
||||
// a trailing '/', append this suffix, and attempt to retrieve the resulting
|
||||
// object. This allows the creation of index.html objects to represent directory
|
||||
// pages.
|
||||
MainPageSuffix string
|
||||
|
||||
// If the requested object path is missing, and any mainPageSuffix object is
|
||||
// missing, if applicable, the service will return the named object from this
|
||||
// bucket as the content for a 404 Not Found result.
|
||||
NotFoundPage string
|
||||
}
|
||||
|
||||
func newBucket(b *raw.Bucket) (*BucketAttrs, error) {
|
||||
if b == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
bucket := &BucketAttrs{
|
||||
Name: b.Name,
|
||||
Location: b.Location,
|
||||
MetaGeneration: b.Metageneration,
|
||||
StorageClass: b.StorageClass,
|
||||
Created: convertTime(b.TimeCreated),
|
||||
VersioningEnabled: b.Versioning != nil && b.Versioning.Enabled,
|
||||
Labels: b.Labels,
|
||||
RequesterPays: b.Billing != nil && b.Billing.RequesterPays,
|
||||
Lifecycle: toLifecycle(b.Lifecycle),
|
||||
rp, err := toRetentionPolicy(b.RetentionPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
acl := make([]ACLRule, len(b.Acl))
|
||||
for i, rule := range b.Acl {
|
||||
acl[i] = ACLRule{
|
||||
Entity: ACLEntity(rule.Entity),
|
||||
Role: ACLRole(rule.Role),
|
||||
}
|
||||
}
|
||||
bucket.ACL = acl
|
||||
objACL := make([]ACLRule, len(b.DefaultObjectAcl))
|
||||
for i, rule := range b.DefaultObjectAcl {
|
||||
objACL[i] = ACLRule{
|
||||
Entity: ACLEntity(rule.Entity),
|
||||
Role: ACLRole(rule.Role),
|
||||
}
|
||||
}
|
||||
bucket.DefaultObjectACL = objACL
|
||||
return bucket
|
||||
return &BucketAttrs{
|
||||
Name: b.Name,
|
||||
Location: b.Location,
|
||||
MetaGeneration: b.Metageneration,
|
||||
DefaultEventBasedHold: b.DefaultEventBasedHold,
|
||||
StorageClass: b.StorageClass,
|
||||
Created: convertTime(b.TimeCreated),
|
||||
VersioningEnabled: b.Versioning != nil && b.Versioning.Enabled,
|
||||
ACL: toBucketACLRules(b.Acl),
|
||||
DefaultObjectACL: toObjectACLRules(b.DefaultObjectAcl),
|
||||
Labels: b.Labels,
|
||||
RequesterPays: b.Billing != nil && b.Billing.RequesterPays,
|
||||
Lifecycle: toLifecycle(b.Lifecycle),
|
||||
RetentionPolicy: rp,
|
||||
CORS: toCORS(b.Cors),
|
||||
Encryption: toBucketEncryption(b.Encryption),
|
||||
Logging: toBucketLogging(b.Logging),
|
||||
Website: toBucketWebsite(b.Website),
|
||||
BucketPolicyOnly: toBucketPolicyOnly(b.IamConfiguration),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// toRawBucket copies the editable attribute from b to the raw library's Bucket type.
|
||||
func (b *BucketAttrs) toRawBucket() *raw.Bucket {
|
||||
var acl []*raw.BucketAccessControl
|
||||
if len(b.ACL) > 0 {
|
||||
acl = make([]*raw.BucketAccessControl, len(b.ACL))
|
||||
for i, rule := range b.ACL {
|
||||
acl[i] = &raw.BucketAccessControl{
|
||||
Entity: string(rule.Entity),
|
||||
Role: string(rule.Role),
|
||||
}
|
||||
}
|
||||
}
|
||||
dACL := toRawObjectACL(b.DefaultObjectACL)
|
||||
// Copy label map.
|
||||
var labels map[string]string
|
||||
if len(b.Labels) > 0 {
|
||||
|
@ -398,26 +525,114 @@ func (b *BucketAttrs) toRawBucket() *raw.Bucket {
|
|||
if b.RequesterPays {
|
||||
bb = &raw.BucketBilling{RequesterPays: true}
|
||||
}
|
||||
var bktIAM *raw.BucketIamConfiguration
|
||||
if b.BucketPolicyOnly.Enabled {
|
||||
bktIAM = &raw.BucketIamConfiguration{
|
||||
BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{
|
||||
Enabled: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
return &raw.Bucket{
|
||||
Name: b.Name,
|
||||
DefaultObjectAcl: dACL,
|
||||
Location: b.Location,
|
||||
StorageClass: b.StorageClass,
|
||||
Acl: acl,
|
||||
Acl: toRawBucketACL(b.ACL),
|
||||
DefaultObjectAcl: toRawObjectACL(b.DefaultObjectACL),
|
||||
Versioning: v,
|
||||
Labels: labels,
|
||||
Billing: bb,
|
||||
Lifecycle: toRawLifecycle(b.Lifecycle),
|
||||
RetentionPolicy: b.RetentionPolicy.toRawRetentionPolicy(),
|
||||
Cors: toRawCORS(b.CORS),
|
||||
Encryption: b.Encryption.toRawBucketEncryption(),
|
||||
Logging: b.Logging.toRawBucketLogging(),
|
||||
Website: b.Website.toRawBucketWebsite(),
|
||||
IamConfiguration: bktIAM,
|
||||
}
|
||||
}
|
||||
|
||||
// CORS is the bucket's Cross-Origin Resource Sharing (CORS) configuration.
|
||||
type CORS struct {
|
||||
// MaxAge is the value to return in the Access-Control-Max-Age
|
||||
// header used in preflight responses.
|
||||
MaxAge time.Duration
|
||||
|
||||
// Methods is the list of HTTP methods on which to include CORS response
|
||||
// headers, (GET, OPTIONS, POST, etc) Note: "*" is permitted in the list
|
||||
// of methods, and means "any method".
|
||||
Methods []string
|
||||
|
||||
// Origins is the list of Origins eligible to receive CORS response
|
||||
// headers. Note: "*" is permitted in the list of origins, and means
|
||||
// "any Origin".
|
||||
Origins []string
|
||||
|
||||
// ResponseHeaders is the list of HTTP headers other than the simple
|
||||
// response headers to give permission for the user-agent to share
|
||||
// across domains.
|
||||
ResponseHeaders []string
|
||||
}
|
||||
|
||||
// BucketEncryption is a bucket's encryption configuration.
|
||||
type BucketEncryption struct {
|
||||
// A Cloud KMS key name, in the form
|
||||
// projects/P/locations/L/keyRings/R/cryptoKeys/K, that will be used to encrypt
|
||||
// objects inserted into this bucket, if no encryption method is specified.
|
||||
// The key's location must be the same as the bucket's.
|
||||
DefaultKMSKeyName string
|
||||
}
|
||||
|
||||
// BucketAttrsToUpdate define the attributes to update during an Update call.
|
||||
type BucketAttrsToUpdate struct {
|
||||
// VersioningEnabled, if set, updates whether the bucket uses versioning.
|
||||
// If set, updates whether the bucket uses versioning.
|
||||
VersioningEnabled optional.Bool
|
||||
|
||||
// RequesterPays, if set, updates whether the bucket is a Requester Pays bucket.
|
||||
// If set, updates whether the bucket is a Requester Pays bucket.
|
||||
RequesterPays optional.Bool
|
||||
|
||||
// DefaultEventBasedHold is the default value for event-based hold on
|
||||
// newly created objects in this bucket.
|
||||
DefaultEventBasedHold optional.Bool
|
||||
|
||||
// BucketPolicyOnly configures access checks to use only bucket-level IAM
|
||||
// policies.
|
||||
BucketPolicyOnly *BucketPolicyOnly
|
||||
|
||||
// If set, updates the retention policy of the bucket. Using
|
||||
// RetentionPolicy.RetentionPeriod = 0 will delete the existing policy.
|
||||
//
|
||||
// This feature is in private alpha release. It is not currently available to
|
||||
// most customers. It might be changed in backwards-incompatible ways and is not
|
||||
// subject to any SLA or deprecation policy.
|
||||
RetentionPolicy *RetentionPolicy
|
||||
|
||||
// If set, replaces the CORS configuration with a new configuration.
|
||||
// An empty (rather than nil) slice causes all CORS policies to be removed.
|
||||
CORS []CORS
|
||||
|
||||
// If set, replaces the encryption configuration of the bucket. Using
|
||||
// BucketEncryption.DefaultKMSKeyName = "" will delete the existing
|
||||
// configuration.
|
||||
Encryption *BucketEncryption
|
||||
|
||||
// If set, replaces the lifecycle configuration of the bucket.
|
||||
Lifecycle *Lifecycle
|
||||
|
||||
// If set, replaces the logging configuration of the bucket.
|
||||
Logging *BucketLogging
|
||||
|
||||
// If set, replaces the website configuration of the bucket.
|
||||
Website *BucketWebsite
|
||||
|
||||
// If not empty, applies a predefined set of access controls.
|
||||
// See https://cloud.google.com/storage/docs/json_api/v1/buckets/patch.
|
||||
PredefinedACL string
|
||||
|
||||
// If not empty, applies a predefined set of default object access controls.
|
||||
// See https://cloud.google.com/storage/docs/json_api/v1/buckets/patch.
|
||||
PredefinedDefaultObjectACL string
|
||||
|
||||
setLabels map[string]string
|
||||
deleteLabels map[string]bool
|
||||
}
|
||||
|
@ -442,6 +657,22 @@ func (ua *BucketAttrsToUpdate) DeleteLabel(name string) {
|
|||
|
||||
func (ua *BucketAttrsToUpdate) toRawBucket() *raw.Bucket {
|
||||
rb := &raw.Bucket{}
|
||||
if ua.CORS != nil {
|
||||
rb.Cors = toRawCORS(ua.CORS)
|
||||
rb.ForceSendFields = append(rb.ForceSendFields, "Cors")
|
||||
}
|
||||
if ua.DefaultEventBasedHold != nil {
|
||||
rb.DefaultEventBasedHold = optional.ToBool(ua.DefaultEventBasedHold)
|
||||
rb.ForceSendFields = append(rb.ForceSendFields, "DefaultEventBasedHold")
|
||||
}
|
||||
if ua.RetentionPolicy != nil {
|
||||
if ua.RetentionPolicy.RetentionPeriod == 0 {
|
||||
rb.NullFields = append(rb.NullFields, "RetentionPolicy")
|
||||
rb.RetentionPolicy = nil
|
||||
} else {
|
||||
rb.RetentionPolicy = ua.RetentionPolicy.toRawRetentionPolicy()
|
||||
}
|
||||
}
|
||||
if ua.VersioningEnabled != nil {
|
||||
rb.Versioning = &raw.BucketVersioning{
|
||||
Enabled: optional.ToBool(ua.VersioningEnabled),
|
||||
|
@ -454,6 +685,50 @@ func (ua *BucketAttrsToUpdate) toRawBucket() *raw.Bucket {
|
|||
ForceSendFields: []string{"RequesterPays"},
|
||||
}
|
||||
}
|
||||
if ua.BucketPolicyOnly != nil {
|
||||
rb.IamConfiguration = &raw.BucketIamConfiguration{
|
||||
BucketPolicyOnly: &raw.BucketIamConfigurationBucketPolicyOnly{
|
||||
Enabled: ua.BucketPolicyOnly.Enabled,
|
||||
},
|
||||
}
|
||||
}
|
||||
if ua.Encryption != nil {
|
||||
if ua.Encryption.DefaultKMSKeyName == "" {
|
||||
rb.NullFields = append(rb.NullFields, "Encryption")
|
||||
rb.Encryption = nil
|
||||
} else {
|
||||
rb.Encryption = ua.Encryption.toRawBucketEncryption()
|
||||
}
|
||||
}
|
||||
if ua.Lifecycle != nil {
|
||||
rb.Lifecycle = toRawLifecycle(*ua.Lifecycle)
|
||||
}
|
||||
if ua.Logging != nil {
|
||||
if *ua.Logging == (BucketLogging{}) {
|
||||
rb.NullFields = append(rb.NullFields, "Logging")
|
||||
rb.Logging = nil
|
||||
} else {
|
||||
rb.Logging = ua.Logging.toRawBucketLogging()
|
||||
}
|
||||
}
|
||||
if ua.Website != nil {
|
||||
if *ua.Website == (BucketWebsite{}) {
|
||||
rb.NullFields = append(rb.NullFields, "Website")
|
||||
rb.Website = nil
|
||||
} else {
|
||||
rb.Website = ua.Website.toRawBucketWebsite()
|
||||
}
|
||||
}
|
||||
if ua.PredefinedACL != "" {
|
||||
// Clear ACL or the call will fail.
|
||||
rb.Acl = nil
|
||||
rb.ForceSendFields = append(rb.ForceSendFields, "Acl")
|
||||
}
|
||||
if ua.PredefinedDefaultObjectACL != "" {
|
||||
// Clear ACLs or the call will fail.
|
||||
rb.DefaultObjectAcl = nil
|
||||
rb.ForceSendFields = append(rb.ForceSendFields, "DefaultObjectAcl")
|
||||
}
|
||||
if ua.setLabels != nil || ua.deleteLabels != nil {
|
||||
rb.Labels = map[string]string{}
|
||||
for k, v := range ua.setLabels {
|
||||
|
@ -471,7 +746,7 @@ func (ua *BucketAttrsToUpdate) toRawBucket() *raw.Bucket {
|
|||
|
||||
// If returns a new BucketHandle that applies a set of preconditions.
|
||||
// Preconditions already set on the BucketHandle are ignored.
|
||||
// Operations on the new handle will only occur if the preconditions are
|
||||
// Operations on the new handle will return an error if the preconditions are not
|
||||
// satisfied. The only valid preconditions for buckets are MetagenerationMatch
|
||||
// and MetagenerationNotMatch.
|
||||
func (b *BucketHandle) If(conds BucketConditions) *BucketHandle {
|
||||
|
@ -506,8 +781,10 @@ func (c *BucketConditions) validate(method string) error {
|
|||
}
|
||||
|
||||
// UserProject returns a new BucketHandle that passes the project ID as the user
|
||||
// project for all subsequent calls. A user project is required for all operations
|
||||
// on requester-pays buckets.
|
||||
// project for all subsequent calls. Calls with a user project will be billed to that
|
||||
// project rather than to the bucket's owning project.
|
||||
//
|
||||
// A user project is required for all operations on Requester Pays buckets.
|
||||
func (b *BucketHandle) UserProject(projectID string) *BucketHandle {
|
||||
b2 := *b
|
||||
b2.userProject = projectID
|
||||
|
@ -516,6 +793,25 @@ func (b *BucketHandle) UserProject(projectID string) *BucketHandle {
|
|||
return &b2
|
||||
}
|
||||
|
||||
// LockRetentionPolicy locks a bucket's retention policy until a previously-configured
|
||||
// RetentionPeriod past the EffectiveTime. Note that if RetentionPeriod is set to less
|
||||
// than a day, the retention policy is treated as a development configuration and locking
|
||||
// will have no effect. The BucketHandle must have a metageneration condition that
|
||||
// matches the bucket's metageneration. See BucketHandle.If.
|
||||
//
|
||||
// This feature is in private alpha release. It is not currently available to
|
||||
// most customers. It might be changed in backwards-incompatible ways and is not
|
||||
// subject to any SLA or deprecation policy.
|
||||
func (b *BucketHandle) LockRetentionPolicy(ctx context.Context) error {
|
||||
var metageneration int64
|
||||
if b.conds != nil {
|
||||
metageneration = b.conds.MetagenerationMatch
|
||||
}
|
||||
req := b.c.raw.Buckets.LockRetentionPolicy(b.name, metageneration)
|
||||
_, err := req.Context(ctx).Do()
|
||||
return err
|
||||
}
|
||||
|
||||
// applyBucketConds modifies the provided call using the conditions in conds.
|
||||
// call is something that quacks like a *raw.WhateverCall.
|
||||
func applyBucketConds(method string, conds *BucketConditions, call interface{}) error {
|
||||
|
@ -539,6 +835,56 @@ func applyBucketConds(method string, conds *BucketConditions, call interface{})
|
|||
return nil
|
||||
}
|
||||
|
||||
func (rp *RetentionPolicy) toRawRetentionPolicy() *raw.BucketRetentionPolicy {
|
||||
if rp == nil {
|
||||
return nil
|
||||
}
|
||||
return &raw.BucketRetentionPolicy{
|
||||
RetentionPeriod: int64(rp.RetentionPeriod / time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
func toRetentionPolicy(rp *raw.BucketRetentionPolicy) (*RetentionPolicy, error) {
|
||||
if rp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339, rp.EffectiveTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RetentionPolicy{
|
||||
RetentionPeriod: time.Duration(rp.RetentionPeriod) * time.Second,
|
||||
EffectiveTime: t,
|
||||
IsLocked: rp.IsLocked,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toRawCORS(c []CORS) []*raw.BucketCors {
|
||||
var out []*raw.BucketCors
|
||||
for _, v := range c {
|
||||
out = append(out, &raw.BucketCors{
|
||||
MaxAgeSeconds: int64(v.MaxAge / time.Second),
|
||||
Method: v.Methods,
|
||||
Origin: v.Origins,
|
||||
ResponseHeader: v.ResponseHeaders,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toCORS(rc []*raw.BucketCors) []CORS {
|
||||
var out []CORS
|
||||
for _, v := range rc {
|
||||
out = append(out, CORS{
|
||||
MaxAge: time.Duration(v.MaxAgeSeconds) * time.Second,
|
||||
Methods: v.Method,
|
||||
Origins: v.Origin,
|
||||
ResponseHeaders: v.ResponseHeader,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toRawLifecycle(l Lifecycle) *raw.BucketLifecycle {
|
||||
var rl raw.BucketLifecycle
|
||||
if len(l.Rules) == 0 {
|
||||
|
@ -604,10 +950,83 @@ func toLifecycle(rl *raw.BucketLifecycle) Lifecycle {
|
|||
if rr.Condition.CreatedBefore != "" {
|
||||
r.Condition.CreatedBefore, _ = time.Parse(rfc3339Date, rr.Condition.CreatedBefore)
|
||||
}
|
||||
l.Rules = append(l.Rules, r)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (e *BucketEncryption) toRawBucketEncryption() *raw.BucketEncryption {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return &raw.BucketEncryption{
|
||||
DefaultKmsKeyName: e.DefaultKMSKeyName,
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketEncryption(e *raw.BucketEncryption) *BucketEncryption {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return &BucketEncryption{DefaultKMSKeyName: e.DefaultKmsKeyName}
|
||||
}
|
||||
|
||||
func (b *BucketLogging) toRawBucketLogging() *raw.BucketLogging {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
return &raw.BucketLogging{
|
||||
LogBucket: b.LogBucket,
|
||||
LogObjectPrefix: b.LogObjectPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketLogging(b *raw.BucketLogging) *BucketLogging {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
return &BucketLogging{
|
||||
LogBucket: b.LogBucket,
|
||||
LogObjectPrefix: b.LogObjectPrefix,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *BucketWebsite) toRawBucketWebsite() *raw.BucketWebsite {
|
||||
if w == nil {
|
||||
return nil
|
||||
}
|
||||
return &raw.BucketWebsite{
|
||||
MainPageSuffix: w.MainPageSuffix,
|
||||
NotFoundPage: w.NotFoundPage,
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketWebsite(w *raw.BucketWebsite) *BucketWebsite {
|
||||
if w == nil {
|
||||
return nil
|
||||
}
|
||||
return &BucketWebsite{
|
||||
MainPageSuffix: w.MainPageSuffix,
|
||||
NotFoundPage: w.NotFoundPage,
|
||||
}
|
||||
}
|
||||
|
||||
func toBucketPolicyOnly(b *raw.BucketIamConfiguration) BucketPolicyOnly {
|
||||
if b == nil || b.BucketPolicyOnly == nil || !b.BucketPolicyOnly.Enabled {
|
||||
return BucketPolicyOnly{}
|
||||
}
|
||||
lt, err := time.Parse(time.RFC3339, b.BucketPolicyOnly.LockedTime)
|
||||
if err != nil {
|
||||
return BucketPolicyOnly{
|
||||
Enabled: true,
|
||||
}
|
||||
}
|
||||
return BucketPolicyOnly{
|
||||
Enabled: true,
|
||||
LockedTime: lt,
|
||||
}
|
||||
}
|
||||
|
||||
// Objects returns an iterator over the objects in the bucket that match the Query q.
|
||||
// If q is nil, no filtering is done.
|
||||
func (b *BucketHandle) Objects(ctx context.Context, q *Query) *ObjectIterator {
|
||||
|
@ -689,8 +1108,6 @@ func (it *ObjectIterator) fetch(pageSize int, pageToken string) (string, error)
|
|||
return resp.NextPageToken, nil
|
||||
}
|
||||
|
||||
// TODO(jbd): Add storage.buckets.update.
|
||||
|
||||
// Buckets returns an iterator over the buckets in the project. You may
|
||||
// optionally set the iterator's Prefix field to restrict the list to buckets
|
||||
// whose names begin with the prefix. By default, all buckets in the project
|
||||
|
@ -736,7 +1153,7 @@ func (it *BucketIterator) Next() (*BucketAttrs, error) {
|
|||
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
||||
func (it *BucketIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
|
||||
|
||||
func (it *BucketIterator) fetch(pageSize int, pageToken string) (string, error) {
|
||||
func (it *BucketIterator) fetch(pageSize int, pageToken string) (token string, err error) {
|
||||
req := it.client.raw.Buckets.List(it.projectID)
|
||||
setClientHeader(req.Header())
|
||||
req.Projection("full")
|
||||
|
@ -746,7 +1163,6 @@ func (it *BucketIterator) fetch(pageSize int, pageToken string) (string, error)
|
|||
req.MaxResults(int64(pageSize))
|
||||
}
|
||||
var resp *raw.Buckets
|
||||
var err error
|
||||
err = runWithRetry(it.ctx, func() error {
|
||||
resp, err = req.Context(it.ctx).Do()
|
||||
return err
|
||||
|
@ -755,7 +1171,11 @@ func (it *BucketIterator) fetch(pageSize int, pageToken string) (string, error)
|
|||
return "", err
|
||||
}
|
||||
for _, item := range resp.Items {
|
||||
it.buckets = append(it.buckets, newBucket(item))
|
||||
b, err := newBucket(item)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
it.buckets = append(it.buckets, b)
|
||||
}
|
||||
return resp.NextPageToken, nil
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -15,10 +15,11 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
|
@ -59,17 +60,32 @@ type Copier struct {
|
|||
// ProgressFunc should return quickly without blocking.
|
||||
ProgressFunc func(copiedBytes, totalBytes uint64)
|
||||
|
||||
// The Cloud KMS key, in the form projects/P/locations/L/keyRings/R/cryptoKeys/K,
|
||||
// that will be used to encrypt the object. Overrides the object's KMSKeyName, if
|
||||
// any.
|
||||
//
|
||||
// Providing both a DestinationKMSKeyName and a customer-supplied encryption key
|
||||
// (via ObjectHandle.Key) on the destination object will result in an error when
|
||||
// Run is called.
|
||||
DestinationKMSKeyName string
|
||||
|
||||
dst, src *ObjectHandle
|
||||
}
|
||||
|
||||
// Run performs the copy.
|
||||
func (c *Copier) Run(ctx context.Context) (*ObjectAttrs, error) {
|
||||
func (c *Copier) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Copier.Run")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := c.src.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.dst.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.DestinationKMSKeyName != "" && c.dst.encryptionKey != nil {
|
||||
return nil, errors.New("storage: cannot use DestinationKMSKeyName with a customer-supplied encryption key")
|
||||
}
|
||||
// Convert destination attributes to raw form, omitting the bucket.
|
||||
// If the bucket is included but name or content-type aren't, the service
|
||||
// returns a 400 with "Required" as the only message. Omitting the bucket
|
||||
|
@ -96,6 +112,12 @@ func (c *Copier) callRewrite(ctx context.Context, rawObj *raw.Object) (*raw.Rewr
|
|||
if c.RewriteToken != "" {
|
||||
call.RewriteToken(c.RewriteToken)
|
||||
}
|
||||
if c.DestinationKMSKeyName != "" {
|
||||
call.DestinationKmsKeyName(c.DestinationKMSKeyName)
|
||||
}
|
||||
if c.PredefinedACL != "" {
|
||||
call.DestinationPredefinedAcl(c.PredefinedACL)
|
||||
}
|
||||
if err := applyConds("Copy destination", c.dst.gen, c.dst.conds, call); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -149,7 +171,10 @@ type Composer struct {
|
|||
}
|
||||
|
||||
// Run performs the compose operation.
|
||||
func (c *Composer) Run(ctx context.Context) (*ObjectAttrs, error) {
|
||||
func (c *Composer) Run(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Composer.Run")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := c.dst.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -187,11 +212,13 @@ func (c *Composer) Run(ctx context.Context) (*ObjectAttrs, error) {
|
|||
if c.dst.userProject != "" {
|
||||
call.UserProject(c.dst.userProject)
|
||||
}
|
||||
if c.PredefinedACL != "" {
|
||||
call.DestinationPredefinedAcl(c.PredefinedACL)
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), c.dst.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var obj *raw.Object
|
||||
var err error
|
||||
setClientHeader(call.Header())
|
||||
err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
|
||||
if err != nil {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -19,11 +19,14 @@ Google Cloud Storage stores data in named objects, which are grouped into bucket
|
|||
More information about Google Cloud Storage is available at
|
||||
https://cloud.google.com/storage/docs.
|
||||
|
||||
All of the methods of this package use exponential backoff to retry calls
|
||||
that fail with certain errors, as described in
|
||||
https://cloud.google.com/storage/docs/exponential-backoff.
|
||||
See https://godoc.org/cloud.google.com/go for authentication, timeouts,
|
||||
connection pooling and similar aspects of this package.
|
||||
|
||||
Note: This package is in beta. Some backwards-incompatible changes may occur.
|
||||
All of the methods of this package use exponential backoff to retry calls that fail
|
||||
with certain errors, as described in
|
||||
https://cloud.google.com/storage/docs/exponential-backoff. Retrying continues
|
||||
indefinitely unless the controlling context is canceled or the client is closed. See
|
||||
context.WithTimeout and context.WithCancel.
|
||||
|
||||
|
||||
Creating a Client
|
||||
|
@ -36,6 +39,13 @@ To start working with this package, create a client:
|
|||
// TODO: Handle error.
|
||||
}
|
||||
|
||||
The client will use your default application credentials.
|
||||
|
||||
If you only wish to access public data, you can create
|
||||
an unauthenticated client with
|
||||
|
||||
client, err := storage.NewClient(ctx, option.WithoutAuthentication())
|
||||
|
||||
Buckets
|
||||
|
||||
A Google Cloud Storage bucket is a collection of objects. To work with a
|
||||
|
@ -56,7 +66,7 @@ global across all projects.
|
|||
|
||||
Each bucket has associated metadata, represented in this package by
|
||||
BucketAttrs. The third argument to BucketHandle.Create allows you to set
|
||||
the intial BucketAttrs of a bucket. To retrieve a bucket's attributes, use
|
||||
the initial BucketAttrs of a bucket. To retrieve a bucket's attributes, use
|
||||
Attrs:
|
||||
|
||||
attrs, err := bkt.Attrs(ctx)
|
||||
|
@ -69,15 +79,16 @@ Attrs:
|
|||
Objects
|
||||
|
||||
An object holds arbitrary data as a sequence of bytes, like a file. You
|
||||
refer to objects using a handle, just as with buckets. You can use the
|
||||
standard Go io.Reader and io.Writer interfaces to read and write
|
||||
object data:
|
||||
refer to objects using a handle, just as with buckets, but unlike buckets
|
||||
you don't explicitly create an object. Instead, the first time you write
|
||||
to an object it will be created. You can use the standard Go io.Reader
|
||||
and io.Writer interfaces to read and write object data:
|
||||
|
||||
obj := bkt.Object("data")
|
||||
// Write something to obj.
|
||||
// w implements io.Writer.
|
||||
w := obj.NewWriter(ctx)
|
||||
// Write some text to obj. This will overwrite whatever is there.
|
||||
// Write some text to obj. This will either create the object or overwrite whatever is there already.
|
||||
if _, err := fmt.Fprintf(w, "This object contains text.\n"); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
|
@ -153,9 +164,13 @@ SignedURL for details.
|
|||
}
|
||||
fmt.Println(url)
|
||||
|
||||
Authentication
|
||||
Errors
|
||||
|
||||
See examples of authorization and authentication at
|
||||
https://godoc.org/cloud.google.com/go#pkg-examples.
|
||||
Errors returned by this client are often of the type [`googleapi.Error`](https://godoc.org/google.golang.org/api/googleapi#Error).
|
||||
These errors can be introspected for more information by type asserting to the richer `googleapi.Error` type. For example:
|
||||
|
||||
if e, ok := err.(*googleapi.Error); ok {
|
||||
if e.Code == 409 { ... }
|
||||
}
|
||||
*/
|
||||
package storage // import "cloud.google.com/go/storage"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -24,6 +24,8 @@ func shouldRetry(err error) bool {
|
|||
// Retry on 429 and 5xx, according to
|
||||
// https://cloud.google.com/storage/docs/exponential-backoff.
|
||||
return e.Code == 429 || (e.Code >= 500 && e.Code < 600)
|
||||
case interface{ Temporary() bool }:
|
||||
return e.Temporary()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func withContext(r *http.Request, ctx context.Context) *http.Request {
|
||||
return r.WithContext(ctx)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -15,29 +15,40 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"cloud.google.com/go/iam"
|
||||
"golang.org/x/net/context"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
iampb "google.golang.org/genproto/googleapis/iam/v1"
|
||||
)
|
||||
|
||||
// IAM provides access to IAM access control for the bucket.
|
||||
func (b *BucketHandle) IAM() *iam.Handle {
|
||||
return iam.InternalNewHandleClient(&iamClient{raw: b.c.raw}, b.name)
|
||||
return iam.InternalNewHandleClient(&iamClient{
|
||||
raw: b.c.raw,
|
||||
userProject: b.userProject,
|
||||
}, b.name)
|
||||
}
|
||||
|
||||
// iamClient implements the iam.client interface.
|
||||
type iamClient struct {
|
||||
raw *raw.Service
|
||||
raw *raw.Service
|
||||
userProject string
|
||||
}
|
||||
|
||||
func (c *iamClient) Get(ctx context.Context, resource string) (*iampb.Policy, error) {
|
||||
req := c.raw.Buckets.GetIamPolicy(resource)
|
||||
setClientHeader(req.Header())
|
||||
func (c *iamClient) Get(ctx context.Context, resource string) (p *iampb.Policy, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Get")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
call := c.raw.Buckets.GetIamPolicy(resource)
|
||||
setClientHeader(call.Header())
|
||||
if c.userProject != "" {
|
||||
call.UserProject(c.userProject)
|
||||
}
|
||||
var rp *raw.Policy
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
rp, err = req.Context(ctx).Do()
|
||||
rp, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -46,23 +57,34 @@ func (c *iamClient) Get(ctx context.Context, resource string) (*iampb.Policy, er
|
|||
return iamFromStoragePolicy(rp), nil
|
||||
}
|
||||
|
||||
func (c *iamClient) Set(ctx context.Context, resource string, p *iampb.Policy) error {
|
||||
func (c *iamClient) Set(ctx context.Context, resource string, p *iampb.Policy) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Set")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
rp := iamToStoragePolicy(p)
|
||||
req := c.raw.Buckets.SetIamPolicy(resource, rp)
|
||||
setClientHeader(req.Header())
|
||||
call := c.raw.Buckets.SetIamPolicy(resource, rp)
|
||||
setClientHeader(call.Header())
|
||||
if c.userProject != "" {
|
||||
call.UserProject(c.userProject)
|
||||
}
|
||||
return runWithRetry(ctx, func() error {
|
||||
_, err := req.Context(ctx).Do()
|
||||
_, err := call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (c *iamClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
|
||||
req := c.raw.Buckets.TestIamPermissions(resource, perms)
|
||||
setClientHeader(req.Header())
|
||||
func (c *iamClient) Test(ctx context.Context, resource string, perms []string) (permissions []string, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.IAM.Test")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
call := c.raw.Buckets.TestIamPermissions(resource, perms)
|
||||
setClientHeader(call.Header())
|
||||
if c.userProject != "" {
|
||||
call.UserProject(c.userProject)
|
||||
}
|
||||
var res *raw.TestIamPermissionsResponse
|
||||
var err error
|
||||
err = runWithRetry(ctx, func() error {
|
||||
res, err = req.Context(ctx).Do()
|
||||
res, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -15,9 +15,10 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"cloud.google.com/go/internal"
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
gax "github.com/googleapis/gax-go/v2"
|
||||
)
|
||||
|
||||
// runWithRetry calls the function until it returns nil or a non-retryable error, or
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -34,6 +34,8 @@ func shouldRetry(err error) bool {
|
|||
// Unfortunately the error type is unexported, so we resort to string
|
||||
// matching.
|
||||
return strings.Contains(e.Error(), "REFUSED_STREAM")
|
||||
case interface{ Temporary() bool }:
|
||||
return e.Temporary()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build !go1.7
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func withContext(r *http.Request, _ interface{}) *http.Request {
|
||||
// In Go 1.6 and below, ignore the context.
|
||||
return r
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
// Copyright 2017 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
||||
// A Notification describes how to send Cloud PubSub messages when certain
|
||||
// events occur in a bucket.
|
||||
type Notification struct {
|
||||
//The ID of the notification.
|
||||
ID string
|
||||
|
||||
// The ID of the topic to which this subscription publishes.
|
||||
TopicID string
|
||||
|
||||
// The ID of the project to which the topic belongs.
|
||||
TopicProjectID string
|
||||
|
||||
// Only send notifications about listed event types. If empty, send notifications
|
||||
// for all event types.
|
||||
// See https://cloud.google.com/storage/docs/pubsub-notifications#events.
|
||||
EventTypes []string
|
||||
|
||||
// If present, only apply this notification configuration to object names that
|
||||
// begin with this prefix.
|
||||
ObjectNamePrefix string
|
||||
|
||||
// An optional list of additional attributes to attach to each Cloud PubSub
|
||||
// message published for this notification subscription.
|
||||
CustomAttributes map[string]string
|
||||
|
||||
// The contents of the message payload.
|
||||
// See https://cloud.google.com/storage/docs/pubsub-notifications#payload.
|
||||
PayloadFormat string
|
||||
}
|
||||
|
||||
// Values for Notification.PayloadFormat.
|
||||
const (
|
||||
// Send no payload with notification messages.
|
||||
NoPayload = "NONE"
|
||||
|
||||
// Send object metadata as JSON with notification messages.
|
||||
JSONPayload = "JSON_API_V1"
|
||||
)
|
||||
|
||||
// Values for Notification.EventTypes.
|
||||
const (
|
||||
// Event that occurs when an object is successfully created.
|
||||
ObjectFinalizeEvent = "OBJECT_FINALIZE"
|
||||
|
||||
// Event that occurs when the metadata of an existing object changes.
|
||||
ObjectMetadataUpdateEvent = "OBJECT_METADATA_UPDATE"
|
||||
|
||||
// Event that occurs when an object is permanently deleted.
|
||||
ObjectDeleteEvent = "OBJECT_DELETE"
|
||||
|
||||
// Event that occurs when the live version of an object becomes an
|
||||
// archived version.
|
||||
ObjectArchiveEvent = "OBJECT_ARCHIVE"
|
||||
)
|
||||
|
||||
func toNotification(rn *raw.Notification) *Notification {
|
||||
n := &Notification{
|
||||
ID: rn.Id,
|
||||
EventTypes: rn.EventTypes,
|
||||
ObjectNamePrefix: rn.ObjectNamePrefix,
|
||||
CustomAttributes: rn.CustomAttributes,
|
||||
PayloadFormat: rn.PayloadFormat,
|
||||
}
|
||||
n.TopicProjectID, n.TopicID = parseNotificationTopic(rn.Topic)
|
||||
return n
|
||||
}
|
||||
|
||||
var topicRE = regexp.MustCompile("^//pubsub.googleapis.com/projects/([^/]+)/topics/([^/]+)")
|
||||
|
||||
// parseNotificationTopic extracts the project and topic IDs from from the full
|
||||
// resource name returned by the service. If the name is malformed, it returns
|
||||
// "?" for both IDs.
|
||||
func parseNotificationTopic(nt string) (projectID, topicID string) {
|
||||
matches := topicRE.FindStringSubmatch(nt)
|
||||
if matches == nil {
|
||||
return "?", "?"
|
||||
}
|
||||
return matches[1], matches[2]
|
||||
}
|
||||
|
||||
func toRawNotification(n *Notification) *raw.Notification {
|
||||
return &raw.Notification{
|
||||
Id: n.ID,
|
||||
Topic: fmt.Sprintf("//pubsub.googleapis.com/projects/%s/topics/%s",
|
||||
n.TopicProjectID, n.TopicID),
|
||||
EventTypes: n.EventTypes,
|
||||
ObjectNamePrefix: n.ObjectNamePrefix,
|
||||
CustomAttributes: n.CustomAttributes,
|
||||
PayloadFormat: string(n.PayloadFormat),
|
||||
}
|
||||
}
|
||||
|
||||
// AddNotification adds a notification to b. You must set n's TopicProjectID, TopicID
|
||||
// and PayloadFormat, and must not set its ID. The other fields are all optional. The
|
||||
// returned Notification's ID can be used to refer to it.
|
||||
func (b *BucketHandle) AddNotification(ctx context.Context, n *Notification) (ret *Notification, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.AddNotification")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if n.ID != "" {
|
||||
return nil, errors.New("storage: AddNotification: ID must not be set")
|
||||
}
|
||||
if n.TopicProjectID == "" {
|
||||
return nil, errors.New("storage: AddNotification: missing TopicProjectID")
|
||||
}
|
||||
if n.TopicID == "" {
|
||||
return nil, errors.New("storage: AddNotification: missing TopicID")
|
||||
}
|
||||
call := b.c.raw.Notifications.Insert(b.name, toRawNotification(n))
|
||||
setClientHeader(call.Header())
|
||||
if b.userProject != "" {
|
||||
call.UserProject(b.userProject)
|
||||
}
|
||||
rn, err := call.Context(ctx).Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toNotification(rn), nil
|
||||
}
|
||||
|
||||
// Notifications returns all the Notifications configured for this bucket, as a map
|
||||
// indexed by notification ID.
|
||||
func (b *BucketHandle) Notifications(ctx context.Context) (n map[string]*Notification, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.Notifications")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
call := b.c.raw.Notifications.List(b.name)
|
||||
setClientHeader(call.Header())
|
||||
if b.userProject != "" {
|
||||
call.UserProject(b.userProject)
|
||||
}
|
||||
var res *raw.Notifications
|
||||
err = runWithRetry(ctx, func() error {
|
||||
res, err = call.Context(ctx).Do()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return notificationsToMap(res.Items), nil
|
||||
}
|
||||
|
||||
func notificationsToMap(rns []*raw.Notification) map[string]*Notification {
|
||||
m := map[string]*Notification{}
|
||||
for _, rn := range rns {
|
||||
m[rn.Id] = toNotification(rn)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// DeleteNotification deletes the notification with the given ID.
|
||||
func (b *BucketHandle) DeleteNotification(ctx context.Context, id string) (err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Bucket.DeleteNotification")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
call := b.c.raw.Notifications.Delete(b.name, id)
|
||||
setClientHeader(call.Header())
|
||||
if b.userProject != "" {
|
||||
call.UserProject(b.userProject)
|
||||
}
|
||||
return call.Context(ctx).Do()
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
// Copyright 2016 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -15,23 +15,277 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
var crc32cTable = crc32.MakeTable(crc32.Castagnoli)
|
||||
|
||||
// ReaderObjectAttrs are attributes about the object being read. These are populated
|
||||
// during the New call. This struct only holds a subset of object attributes: to
|
||||
// get the full set of attributes, use ObjectHandle.Attrs.
|
||||
//
|
||||
// Each field is read-only.
|
||||
type ReaderObjectAttrs struct {
|
||||
// Size is the length of the object's content.
|
||||
Size int64
|
||||
|
||||
// ContentType is the MIME type of the object's content.
|
||||
ContentType string
|
||||
|
||||
// ContentEncoding is the encoding of the object's content.
|
||||
ContentEncoding string
|
||||
|
||||
// CacheControl specifies whether and for how long browser and Internet
|
||||
// caches are allowed to cache your objects.
|
||||
CacheControl string
|
||||
|
||||
// LastModified is the time that the object was last modified.
|
||||
LastModified time.Time
|
||||
|
||||
// Generation is the generation number of the object's content.
|
||||
Generation int64
|
||||
|
||||
// Metageneration is the version of the metadata for this object at
|
||||
// this generation. This field is used for preconditions and for
|
||||
// detecting changes in metadata. A metageneration number is only
|
||||
// meaningful in the context of a particular generation of a
|
||||
// particular object.
|
||||
Metageneration int64
|
||||
}
|
||||
|
||||
// NewReader creates a new Reader to read the contents of the
|
||||
// object.
|
||||
// ErrObjectNotExist will be returned if the object is not found.
|
||||
//
|
||||
// The caller must call Close on the returned Reader when done reading.
|
||||
func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader, error) {
|
||||
return o.NewRangeReader(ctx, 0, -1)
|
||||
}
|
||||
|
||||
// NewRangeReader reads part of an object, reading at most length bytes
|
||||
// starting at the given offset. If length is negative, the object is read
|
||||
// until the end.
|
||||
func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) (r *Reader, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Object.NewRangeReader")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := o.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if offset < 0 {
|
||||
return nil, fmt.Errorf("storage: invalid offset %d < 0", offset)
|
||||
}
|
||||
if o.conds != nil {
|
||||
if err := o.conds.validate("NewRangeReader"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
u := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "storage.googleapis.com",
|
||||
Path: fmt.Sprintf("/%s/%s", o.bucket, o.object),
|
||||
}
|
||||
verb := "GET"
|
||||
if length == 0 {
|
||||
verb = "HEAD"
|
||||
}
|
||||
req, err := http.NewRequest(verb, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if o.userProject != "" {
|
||||
req.Header.Set("X-Goog-User-Project", o.userProject)
|
||||
}
|
||||
if o.readCompressed {
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
}
|
||||
if err := setEncryptionHeaders(req.Header, o.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gen := o.gen
|
||||
|
||||
// Define a function that initiates a Read with offset and length, assuming we
|
||||
// have already read seen bytes.
|
||||
reopen := func(seen int64) (*http.Response, error) {
|
||||
start := offset + seen
|
||||
if length < 0 && start > 0 {
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", start))
|
||||
} else if length > 0 {
|
||||
// The end character isn't affected by how many bytes we've seen.
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", start, offset+length-1))
|
||||
}
|
||||
// We wait to assign conditions here because the generation number can change in between reopen() runs.
|
||||
req.URL.RawQuery = conditionsQuery(gen, o.conds)
|
||||
var res *http.Response
|
||||
err = runWithRetry(ctx, func() error {
|
||||
res, err = o.c.hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
res.Body.Close()
|
||||
return ErrObjectNotExist
|
||||
}
|
||||
if res.StatusCode < 200 || res.StatusCode > 299 {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return &googleapi.Error{
|
||||
Code: res.StatusCode,
|
||||
Header: res.Header,
|
||||
Body: string(body),
|
||||
}
|
||||
}
|
||||
if start > 0 && length != 0 && res.StatusCode != http.StatusPartialContent {
|
||||
res.Body.Close()
|
||||
return errors.New("storage: partial request not satisfied")
|
||||
}
|
||||
// If a generation hasn't been specified, and this is the first response we get, let's record the
|
||||
// generation. In future requests we'll use this generation as a precondition to avoid data races.
|
||||
if gen < 0 && res.Header.Get("X-Goog-Generation") != "" {
|
||||
gen64, err := strconv.ParseInt(res.Header.Get("X-Goog-Generation"), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gen = gen64
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res, err := reopen(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
size int64 // total size of object, even if a range was requested.
|
||||
checkCRC bool
|
||||
crc uint32
|
||||
)
|
||||
if res.StatusCode == http.StatusPartialContent {
|
||||
cr := strings.TrimSpace(res.Header.Get("Content-Range"))
|
||||
if !strings.HasPrefix(cr, "bytes ") || !strings.Contains(cr, "/") {
|
||||
|
||||
return nil, fmt.Errorf("storage: invalid Content-Range %q", cr)
|
||||
}
|
||||
size, err = strconv.ParseInt(cr[strings.LastIndex(cr, "/")+1:], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: invalid Content-Range %q", cr)
|
||||
}
|
||||
} else {
|
||||
size = res.ContentLength
|
||||
// Check the CRC iff all of the following hold:
|
||||
// - We asked for content (length != 0).
|
||||
// - We got all the content (status != PartialContent).
|
||||
// - The server sent a CRC header.
|
||||
// - The Go http stack did not uncompress the file.
|
||||
// - We were not served compressed data that was uncompressed on download.
|
||||
// The problem with the last two cases is that the CRC will not match -- GCS
|
||||
// computes it on the compressed contents, but we compute it on the
|
||||
// uncompressed contents.
|
||||
if length != 0 && !res.Uncompressed && !uncompressedByServer(res) {
|
||||
crc, checkCRC = parseCRC32c(res)
|
||||
}
|
||||
}
|
||||
|
||||
remain := res.ContentLength
|
||||
body := res.Body
|
||||
if length == 0 {
|
||||
remain = 0
|
||||
body.Close()
|
||||
body = emptyBody
|
||||
}
|
||||
var metaGen int64
|
||||
if res.Header.Get("X-Goog-Generation") != "" {
|
||||
metaGen, err = strconv.ParseInt(res.Header.Get("X-Goog-Metageneration"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var lm time.Time
|
||||
if res.Header.Get("Last-Modified") != "" {
|
||||
lm, err = http.ParseTime(res.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
attrs := ReaderObjectAttrs{
|
||||
Size: size,
|
||||
ContentType: res.Header.Get("Content-Type"),
|
||||
ContentEncoding: res.Header.Get("Content-Encoding"),
|
||||
CacheControl: res.Header.Get("Cache-Control"),
|
||||
LastModified: lm,
|
||||
Generation: gen,
|
||||
Metageneration: metaGen,
|
||||
}
|
||||
return &Reader{
|
||||
Attrs: attrs,
|
||||
body: body,
|
||||
size: size,
|
||||
remain: remain,
|
||||
wantCRC: crc,
|
||||
checkCRC: checkCRC,
|
||||
reopen: reopen,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func uncompressedByServer(res *http.Response) bool {
|
||||
// If the data is stored as gzip but is not encoded as gzip, then it
|
||||
// was uncompressed by the server.
|
||||
return res.Header.Get("X-Goog-Stored-Content-Encoding") == "gzip" &&
|
||||
res.Header.Get("Content-Encoding") != "gzip"
|
||||
}
|
||||
|
||||
func parseCRC32c(res *http.Response) (uint32, bool) {
|
||||
const prefix = "crc32c="
|
||||
for _, spec := range res.Header["X-Goog-Hash"] {
|
||||
if strings.HasPrefix(spec, prefix) {
|
||||
c, err := decodeUint32(spec[len(prefix):])
|
||||
if err == nil {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
var emptyBody = ioutil.NopCloser(strings.NewReader(""))
|
||||
|
||||
// Reader reads a Cloud Storage object.
|
||||
// It implements io.Reader.
|
||||
//
|
||||
// Typically, a Reader computes the CRC of the downloaded content and compares it to
|
||||
// the stored CRC, returning an error from Read if there is a mismatch. This integrity check
|
||||
// is skipped if transcoding occurs. See https://cloud.google.com/storage/docs/transcoding.
|
||||
type Reader struct {
|
||||
body io.ReadCloser
|
||||
remain, size int64
|
||||
contentType string
|
||||
cacheControl string
|
||||
checkCRC bool // should we check the CRC?
|
||||
wantCRC uint32 // the CRC32c value the server sent in the header
|
||||
gotCRC uint32 // running crc
|
||||
Attrs ReaderObjectAttrs
|
||||
body io.ReadCloser
|
||||
seen, remain, size int64
|
||||
checkCRC bool // should we check the CRC?
|
||||
wantCRC uint32 // the CRC32c value the server sent in the header
|
||||
gotCRC uint32 // running crc
|
||||
reopen func(seen int64) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Close closes the Reader. It must be called when done reading.
|
||||
|
@ -40,7 +294,7 @@ func (r *Reader) Close() error {
|
|||
}
|
||||
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
n, err := r.body.Read(p)
|
||||
n, err := r.readWithRetry(p)
|
||||
if r.remain != -1 {
|
||||
r.remain -= int64(n)
|
||||
}
|
||||
|
@ -49,19 +303,52 @@ func (r *Reader) Read(p []byte) (int, error) {
|
|||
// Check CRC here. It would be natural to check it in Close, but
|
||||
// everybody defers Close on the assumption that it doesn't return
|
||||
// anything worth looking at.
|
||||
if r.remain == 0 && r.gotCRC != r.wantCRC {
|
||||
return n, fmt.Errorf("storage: bad CRC on read: got %d, want %d",
|
||||
r.gotCRC, r.wantCRC)
|
||||
if err == io.EOF {
|
||||
if r.gotCRC != r.wantCRC {
|
||||
return n, fmt.Errorf("storage: bad CRC on read: got %d, want %d",
|
||||
r.gotCRC, r.wantCRC)
|
||||
}
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *Reader) readWithRetry(p []byte) (int, error) {
|
||||
n := 0
|
||||
for len(p[n:]) > 0 {
|
||||
m, err := r.body.Read(p[n:])
|
||||
n += m
|
||||
r.seen += int64(m)
|
||||
if !shouldRetryRead(err) {
|
||||
return n, err
|
||||
}
|
||||
// Read failed, but we will try again. Send a ranged read request that takes
|
||||
// into account the number of bytes we've already seen.
|
||||
res, err := r.reopen(r.seen)
|
||||
if err != nil {
|
||||
// reopen already retries
|
||||
return n, err
|
||||
}
|
||||
r.body.Close()
|
||||
r.body = res.Body
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func shouldRetryRead(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return strings.HasSuffix(err.Error(), "INTERNAL_ERROR") && strings.Contains(reflect.TypeOf(err).String(), "http2")
|
||||
}
|
||||
|
||||
// Size returns the size of the object in bytes.
|
||||
// The returned value is always the same and is not affected by
|
||||
// calls to Read or Close.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.Size.
|
||||
func (r *Reader) Size() int64 {
|
||||
return r.size
|
||||
return r.Attrs.Size
|
||||
}
|
||||
|
||||
// Remain returns the number of bytes left to read, or -1 if unknown.
|
||||
|
@ -70,11 +357,29 @@ func (r *Reader) Remain() int64 {
|
|||
}
|
||||
|
||||
// ContentType returns the content type of the object.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.ContentType.
|
||||
func (r *Reader) ContentType() string {
|
||||
return r.contentType
|
||||
return r.Attrs.ContentType
|
||||
}
|
||||
|
||||
// ContentEncoding returns the content encoding of the object.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.ContentEncoding.
|
||||
func (r *Reader) ContentEncoding() string {
|
||||
return r.Attrs.ContentEncoding
|
||||
}
|
||||
|
||||
// CacheControl returns the cache control of the object.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.CacheControl.
|
||||
func (r *Reader) CacheControl() string {
|
||||
return r.cacheControl
|
||||
return r.Attrs.CacheControl
|
||||
}
|
||||
|
||||
// LastModified returns the value of the Last-Modified header.
|
||||
//
|
||||
// Deprecated: use Reader.Attrs.LastModified.
|
||||
func (r *Reader) LastModified() (time.Time, error) {
|
||||
return r.Attrs.LastModified, nil
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +16,7 @@ package storage
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
|
@ -25,28 +26,29 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"google.golang.org/api/option"
|
||||
htransport "google.golang.org/api/transport/http"
|
||||
|
||||
"cloud.google.com/go/internal/optional"
|
||||
"cloud.google.com/go/internal/trace"
|
||||
"cloud.google.com/go/internal/version"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/option"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
htransport "google.golang.org/api/transport/http"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrBucketNotExist indicates that the bucket does not exist.
|
||||
ErrBucketNotExist = errors.New("storage: bucket doesn't exist")
|
||||
// ErrObjectNotExist indicates that the object does not exist.
|
||||
ErrObjectNotExist = errors.New("storage: object doesn't exist")
|
||||
)
|
||||
|
||||
|
@ -110,8 +112,7 @@ func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error
|
|||
//
|
||||
// Close need not be called at program exit.
|
||||
func (c *Client) Close() error {
|
||||
// Set fields to nil so that subsequent uses
|
||||
// will panic.
|
||||
// Set fields to nil so that subsequent uses will panic.
|
||||
c.hc = nil
|
||||
c.raw = nil
|
||||
return nil
|
||||
|
@ -170,7 +171,7 @@ type SignedURLOptions struct {
|
|||
// Optional.
|
||||
ContentType string
|
||||
|
||||
// Headers is a list of extention headers the client must provide
|
||||
// Headers is a list of extension headers the client must provide
|
||||
// in order to use the generated signed URL.
|
||||
// Optional.
|
||||
Headers []string
|
||||
|
@ -182,6 +183,60 @@ type SignedURLOptions struct {
|
|||
MD5 string
|
||||
}
|
||||
|
||||
var (
|
||||
canonicalHeaderRegexp = regexp.MustCompile(`(?i)^(x-goog-[^:]+):(.*)?$`)
|
||||
excludedCanonicalHeaders = map[string]bool{
|
||||
"x-goog-encryption-key": true,
|
||||
"x-goog-encryption-key-sha256": true,
|
||||
}
|
||||
)
|
||||
|
||||
// sanitizeHeaders applies the specifications for canonical extension headers at
|
||||
// https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers.
|
||||
func sanitizeHeaders(hdrs []string) []string {
|
||||
headerMap := map[string][]string{}
|
||||
for _, hdr := range hdrs {
|
||||
// No leading or trailing whitespaces.
|
||||
sanitizedHeader := strings.TrimSpace(hdr)
|
||||
|
||||
// Only keep canonical headers, discard any others.
|
||||
headerMatches := canonicalHeaderRegexp.FindStringSubmatch(sanitizedHeader)
|
||||
if len(headerMatches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
header := strings.ToLower(strings.TrimSpace(headerMatches[1]))
|
||||
if excludedCanonicalHeaders[headerMatches[1]] {
|
||||
// Do not keep any deliberately excluded canonical headers when signing.
|
||||
continue
|
||||
}
|
||||
value := strings.TrimSpace(headerMatches[2])
|
||||
if len(value) > 0 {
|
||||
// Remove duplicate headers by appending the values of duplicates
|
||||
// in their order of appearance.
|
||||
headerMap[header] = append(headerMap[header], value)
|
||||
}
|
||||
}
|
||||
|
||||
var sanitizedHeaders []string
|
||||
for header, values := range headerMap {
|
||||
// There should be no spaces around the colon separating the
|
||||
// header name from the header value or around the values
|
||||
// themselves. The values should be separated by commas.
|
||||
// NOTE: The semantics for headers without a value are not clear.
|
||||
// However from specifications these should be edge-cases
|
||||
// anyway and we should assume that there will be no
|
||||
// canonical headers using empty values. Any such headers
|
||||
// are discarded at the regexp stage above.
|
||||
sanitizedHeaders = append(
|
||||
sanitizedHeaders,
|
||||
fmt.Sprintf("%s:%s", header, strings.Join(values, ",")),
|
||||
)
|
||||
}
|
||||
sort.Strings(sanitizedHeaders)
|
||||
return sanitizedHeaders
|
||||
}
|
||||
|
||||
// SignedURL returns a URL for the specified object. Signed URLs allow
|
||||
// the users access to a restricted resource for a limited time without having a
|
||||
// Google account or signing in. For more information about the signed
|
||||
|
@ -208,6 +263,7 @@ func SignedURL(bucket, name string, opts *SignedURLOptions) (string, error) {
|
|||
return "", errors.New("storage: invalid MD5 checksum")
|
||||
}
|
||||
}
|
||||
opts.Headers = sanitizeHeaders(opts.Headers)
|
||||
|
||||
signBytes := opts.SignBytes
|
||||
if opts.PrivateKey != nil {
|
||||
|
@ -258,14 +314,15 @@ func SignedURL(bucket, name string, opts *SignedURLOptions) (string, error) {
|
|||
// ObjectHandle provides operations on an object in a Google Cloud Storage bucket.
|
||||
// Use BucketHandle.Object to get a handle.
|
||||
type ObjectHandle struct {
|
||||
c *Client
|
||||
bucket string
|
||||
object string
|
||||
acl ACLHandle
|
||||
gen int64 // a negative value indicates latest
|
||||
conds *Conditions
|
||||
encryptionKey []byte // AES-256 key
|
||||
userProject string // for requester-pays buckets
|
||||
c *Client
|
||||
bucket string
|
||||
object string
|
||||
acl ACLHandle
|
||||
gen int64 // a negative value indicates latest
|
||||
conds *Conditions
|
||||
encryptionKey []byte // AES-256 key
|
||||
userProject string // for requester-pays buckets
|
||||
readCompressed bool // Accept-Encoding: gzip
|
||||
}
|
||||
|
||||
// ACL provides access to the object's access control list.
|
||||
|
@ -288,7 +345,7 @@ func (o *ObjectHandle) Generation(gen int64) *ObjectHandle {
|
|||
|
||||
// If returns a new ObjectHandle that applies a set of preconditions.
|
||||
// Preconditions already set on the ObjectHandle are ignored.
|
||||
// Operations on the new handle will only occur if the preconditions are
|
||||
// Operations on the new handle will return an error if the preconditions are not
|
||||
// satisfied. See https://cloud.google.com/storage/docs/generations-preconditions
|
||||
// for more details.
|
||||
func (o *ObjectHandle) If(conds Conditions) *ObjectHandle {
|
||||
|
@ -310,7 +367,10 @@ func (o *ObjectHandle) Key(encryptionKey []byte) *ObjectHandle {
|
|||
|
||||
// Attrs returns meta information about the object.
|
||||
// ErrObjectNotExist will be returned if the object is not found.
|
||||
func (o *ObjectHandle) Attrs(ctx context.Context) (*ObjectAttrs, error) {
|
||||
func (o *ObjectHandle) Attrs(ctx context.Context) (attrs *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Object.Attrs")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := o.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -325,7 +385,6 @@ func (o *ObjectHandle) Attrs(ctx context.Context) (*ObjectAttrs, error) {
|
|||
return nil, err
|
||||
}
|
||||
var obj *raw.Object
|
||||
var err error
|
||||
setClientHeader(call.Header())
|
||||
err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
|
||||
if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound {
|
||||
|
@ -340,7 +399,10 @@ func (o *ObjectHandle) Attrs(ctx context.Context) (*ObjectAttrs, error) {
|
|||
// Update updates an object with the provided attributes.
|
||||
// All zero-value attributes are ignored.
|
||||
// ErrObjectNotExist will be returned if the object is not found.
|
||||
func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (*ObjectAttrs, error) {
|
||||
func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (oa *ObjectAttrs, err error) {
|
||||
ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Object.Update")
|
||||
defer func() { trace.EndSpan(ctx, err) }()
|
||||
|
||||
if err := o.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -379,6 +441,14 @@ func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (
|
|||
attrs.CacheControl = optional.ToString(uattrs.CacheControl)
|
||||
forceSendFields = append(forceSendFields, "CacheControl")
|
||||
}
|
||||
if uattrs.EventBasedHold != nil {
|
||||
attrs.EventBasedHold = optional.ToBool(uattrs.EventBasedHold)
|
||||
forceSendFields = append(forceSendFields, "EventBasedHold")
|
||||
}
|
||||
if uattrs.TemporaryHold != nil {
|
||||
attrs.TemporaryHold = optional.ToBool(uattrs.TemporaryHold)
|
||||
forceSendFields = append(forceSendFields, "TemporaryHold")
|
||||
}
|
||||
if uattrs.Metadata != nil {
|
||||
attrs.Metadata = uattrs.Metadata
|
||||
if len(attrs.Metadata) == 0 {
|
||||
|
@ -404,11 +474,13 @@ func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (
|
|||
if o.userProject != "" {
|
||||
call.UserProject(o.userProject)
|
||||
}
|
||||
if uattrs.PredefinedACL != "" {
|
||||
call.PredefinedAcl(uattrs.PredefinedACL)
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), o.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var obj *raw.Object
|
||||
var err error
|
||||
setClientHeader(call.Header())
|
||||
err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err })
|
||||
if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound {
|
||||
|
@ -420,6 +492,16 @@ func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (
|
|||
return newObject(obj), nil
|
||||
}
|
||||
|
||||
// BucketName returns the name of the bucket.
|
||||
func (o *ObjectHandle) BucketName() string {
|
||||
return o.bucket
|
||||
}
|
||||
|
||||
// ObjectName returns the name of the object.
|
||||
func (o *ObjectHandle) ObjectName() string {
|
||||
return o.object
|
||||
}
|
||||
|
||||
// ObjectAttrsToUpdate is used to update the attributes of an object.
|
||||
// Only fields set to non-nil values will be updated.
|
||||
// Set a field to its zero value to delete it.
|
||||
|
@ -432,6 +514,8 @@ func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (
|
|||
// Metadata: map[string]string{},
|
||||
// }
|
||||
type ObjectAttrsToUpdate struct {
|
||||
EventBasedHold optional.Bool
|
||||
TemporaryHold optional.Bool
|
||||
ContentType optional.String
|
||||
ContentLanguage optional.String
|
||||
ContentEncoding optional.String
|
||||
|
@ -439,6 +523,10 @@ type ObjectAttrsToUpdate struct {
|
|||
CacheControl optional.String
|
||||
Metadata map[string]string // set to map[string]string{} to delete
|
||||
ACL []ACLRule
|
||||
|
||||
// If not empty, applies a predefined set of access controls. ACL must be nil.
|
||||
// See https://cloud.google.com/storage/docs/json_api/v1/objects/patch.
|
||||
PredefinedACL string
|
||||
}
|
||||
|
||||
// Delete deletes the single specified object.
|
||||
|
@ -467,140 +555,13 @@ func (o *ObjectHandle) Delete(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// NewReader creates a new Reader to read the contents of the
|
||||
// object.
|
||||
// ErrObjectNotExist will be returned if the object is not found.
|
||||
//
|
||||
// The caller must call Close on the returned Reader when done reading.
|
||||
func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader, error) {
|
||||
return o.NewRangeReader(ctx, 0, -1)
|
||||
// ReadCompressed when true causes the read to happen without decompressing.
|
||||
func (o *ObjectHandle) ReadCompressed(compressed bool) *ObjectHandle {
|
||||
o2 := *o
|
||||
o2.readCompressed = compressed
|
||||
return &o2
|
||||
}
|
||||
|
||||
// NewRangeReader reads part of an object, reading at most length bytes
|
||||
// starting at the given offset. If length is negative, the object is read
|
||||
// until the end.
|
||||
func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64) (*Reader, error) {
|
||||
if err := o.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if offset < 0 {
|
||||
return nil, fmt.Errorf("storage: invalid offset %d < 0", offset)
|
||||
}
|
||||
if o.conds != nil {
|
||||
if err := o.conds.validate("NewRangeReader"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
u := &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "storage.googleapis.com",
|
||||
Path: fmt.Sprintf("/%s/%s", o.bucket, o.object),
|
||||
RawQuery: conditionsQuery(o.gen, o.conds),
|
||||
}
|
||||
verb := "GET"
|
||||
if length == 0 {
|
||||
verb = "HEAD"
|
||||
}
|
||||
req, err := http.NewRequest(verb, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = withContext(req, ctx)
|
||||
if length < 0 && offset > 0 {
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset))
|
||||
} else if length > 0 {
|
||||
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
|
||||
}
|
||||
if o.userProject != "" {
|
||||
req.Header.Set("X-Goog-User-Project", o.userProject)
|
||||
}
|
||||
if err := setEncryptionHeaders(req.Header, o.encryptionKey, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res *http.Response
|
||||
err = runWithRetry(ctx, func() error {
|
||||
res, err = o.c.hc.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
res.Body.Close()
|
||||
return ErrObjectNotExist
|
||||
}
|
||||
if res.StatusCode < 200 || res.StatusCode > 299 {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return &googleapi.Error{
|
||||
Code: res.StatusCode,
|
||||
Header: res.Header,
|
||||
Body: string(body),
|
||||
}
|
||||
}
|
||||
if offset > 0 && length != 0 && res.StatusCode != http.StatusPartialContent {
|
||||
res.Body.Close()
|
||||
return errors.New("storage: partial request not satisfied")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var size int64 // total size of object, even if a range was requested.
|
||||
if res.StatusCode == http.StatusPartialContent {
|
||||
cr := strings.TrimSpace(res.Header.Get("Content-Range"))
|
||||
if !strings.HasPrefix(cr, "bytes ") || !strings.Contains(cr, "/") {
|
||||
return nil, fmt.Errorf("storage: invalid Content-Range %q", cr)
|
||||
}
|
||||
size, err = strconv.ParseInt(cr[strings.LastIndex(cr, "/")+1:], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("storage: invalid Content-Range %q", cr)
|
||||
}
|
||||
} else {
|
||||
size = res.ContentLength
|
||||
}
|
||||
|
||||
remain := res.ContentLength
|
||||
body := res.Body
|
||||
if length == 0 {
|
||||
remain = 0
|
||||
body.Close()
|
||||
body = emptyBody
|
||||
}
|
||||
var (
|
||||
checkCRC bool
|
||||
crc uint32
|
||||
)
|
||||
// Even if there is a CRC header, we can't compute the hash on partial data.
|
||||
if remain == size {
|
||||
crc, checkCRC = parseCRC32c(res)
|
||||
}
|
||||
return &Reader{
|
||||
body: body,
|
||||
size: size,
|
||||
remain: remain,
|
||||
contentType: res.Header.Get("Content-Type"),
|
||||
cacheControl: res.Header.Get("Cache-Control"),
|
||||
wantCRC: crc,
|
||||
checkCRC: checkCRC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseCRC32c(res *http.Response) (uint32, bool) {
|
||||
const prefix = "crc32c="
|
||||
for _, spec := range res.Header["X-Goog-Hash"] {
|
||||
if strings.HasPrefix(spec, prefix) {
|
||||
c, err := decodeUint32(spec[len(prefix):])
|
||||
if err == nil {
|
||||
return c, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
var emptyBody = ioutil.NopCloser(strings.NewReader(""))
|
||||
|
||||
// NewWriter returns a storage Writer that writes to the GCS object
|
||||
// associated with this ObjectHandle.
|
||||
//
|
||||
|
@ -614,7 +575,8 @@ var emptyBody = ioutil.NopCloser(strings.NewReader(""))
|
|||
// attribute is specified, the content type will be automatically sniffed
|
||||
// using net/http.DetectContentType.
|
||||
//
|
||||
// It is the caller's responsibility to call Close when writing is done.
|
||||
// It is the caller's responsibility to call Close when writing is done. To
|
||||
// stop writing without saving the data, cancel the context.
|
||||
func (o *ObjectHandle) NewWriter(ctx context.Context) *Writer {
|
||||
return &Writer{
|
||||
ctx: ctx,
|
||||
|
@ -638,11 +600,10 @@ func (o *ObjectHandle) validate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// parseKey converts the binary contents of a private key file
|
||||
// to an *rsa.PrivateKey. It detects whether the private key is in a
|
||||
// PEM container or not. If so, it extracts the the private key
|
||||
// from PEM container before conversion. It only supports PEM
|
||||
// containers with no passphrase.
|
||||
// parseKey converts the binary contents of a private key file to an
|
||||
// *rsa.PrivateKey. It detects whether the private key is in a PEM container or
|
||||
// not. If so, it extracts the private key from PEM container before
|
||||
// conversion. It only supports PEM containers with no passphrase.
|
||||
func parseKey(key []byte) (*rsa.PrivateKey, error) {
|
||||
if block, _ := pem.Decode(key); block != nil {
|
||||
key = block.Bytes
|
||||
|
@ -661,34 +622,26 @@ func parseKey(key []byte) (*rsa.PrivateKey, error) {
|
|||
return parsed, nil
|
||||
}
|
||||
|
||||
func toRawObjectACL(oldACL []ACLRule) []*raw.ObjectAccessControl {
|
||||
var acl []*raw.ObjectAccessControl
|
||||
if len(oldACL) > 0 {
|
||||
acl = make([]*raw.ObjectAccessControl, len(oldACL))
|
||||
for i, rule := range oldACL {
|
||||
acl[i] = &raw.ObjectAccessControl{
|
||||
Entity: string(rule.Entity),
|
||||
Role: string(rule.Role),
|
||||
}
|
||||
}
|
||||
}
|
||||
return acl
|
||||
}
|
||||
|
||||
// toRawObject copies the editable attributes from o to the raw library's Object type.
|
||||
func (o *ObjectAttrs) toRawObject(bucket string) *raw.Object {
|
||||
acl := toRawObjectACL(o.ACL)
|
||||
var ret string
|
||||
if !o.RetentionExpirationTime.IsZero() {
|
||||
ret = o.RetentionExpirationTime.Format(time.RFC3339)
|
||||
}
|
||||
return &raw.Object{
|
||||
Bucket: bucket,
|
||||
Name: o.Name,
|
||||
ContentType: o.ContentType,
|
||||
ContentEncoding: o.ContentEncoding,
|
||||
ContentLanguage: o.ContentLanguage,
|
||||
CacheControl: o.CacheControl,
|
||||
ContentDisposition: o.ContentDisposition,
|
||||
StorageClass: o.StorageClass,
|
||||
Acl: acl,
|
||||
Metadata: o.Metadata,
|
||||
Bucket: bucket,
|
||||
Name: o.Name,
|
||||
EventBasedHold: o.EventBasedHold,
|
||||
TemporaryHold: o.TemporaryHold,
|
||||
RetentionExpirationTime: ret,
|
||||
ContentType: o.ContentType,
|
||||
ContentEncoding: o.ContentEncoding,
|
||||
ContentLanguage: o.ContentLanguage,
|
||||
CacheControl: o.CacheControl,
|
||||
ContentDisposition: o.ContentDisposition,
|
||||
StorageClass: o.StorageClass,
|
||||
Acl: toRawObjectACL(o.ACL),
|
||||
Metadata: o.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -712,9 +665,32 @@ type ObjectAttrs struct {
|
|||
// headers when serving the object data.
|
||||
CacheControl string
|
||||
|
||||
// EventBasedHold specifies whether an object is under event-based hold. New
|
||||
// objects created in a bucket whose DefaultEventBasedHold is set will
|
||||
// default to that value.
|
||||
EventBasedHold bool
|
||||
|
||||
// TemporaryHold specifies whether an object is under temporary hold. While
|
||||
// this flag is set to true, the object is protected against deletion and
|
||||
// overwrites.
|
||||
TemporaryHold bool
|
||||
|
||||
// RetentionExpirationTime is a server-determined value that specifies the
|
||||
// earliest time that the object's retention period expires.
|
||||
// This is a read-only field.
|
||||
RetentionExpirationTime time.Time
|
||||
|
||||
// ACL is the list of access control rules for the object.
|
||||
ACL []ACLRule
|
||||
|
||||
// If not empty, applies a predefined set of access controls. It should be set
|
||||
// only when writing, copying or composing an object. When copying or composing,
|
||||
// it acts as the destinationPredefinedAcl parameter.
|
||||
// PredefinedACL is always empty for ObjectAttrs returned from the service.
|
||||
// See https://cloud.google.com/storage/docs/json_api/v1/objects/insert
|
||||
// for valid values.
|
||||
PredefinedACL string
|
||||
|
||||
// Owner is the owner of the object. This field is read-only.
|
||||
//
|
||||
// If non-zero, it is in the form of "user-<userId>".
|
||||
|
@ -788,6 +764,14 @@ type ObjectAttrs struct {
|
|||
// encryption in Google Cloud Storage.
|
||||
CustomerKeySHA256 string
|
||||
|
||||
// Cloud KMS key name, in the form
|
||||
// projects/P/locations/L/keyRings/R/cryptoKeys/K, used to encrypt this object,
|
||||
// if the object is encrypted by such a key.
|
||||
//
|
||||
// Providing both a KMSKeyName and a customer-supplied encryption key (via
|
||||
// ObjectHandle.Key) will result in an error when writing an object.
|
||||
KMSKeyName string
|
||||
|
||||
// Prefix is set only for ObjectAttrs which represent synthetic "directory
|
||||
// entries" when iterating over buckets using Query.Delimiter. See
|
||||
// ObjectIterator.Next. When set, no other fields in ObjectAttrs will be
|
||||
|
@ -809,13 +793,6 @@ func newObject(o *raw.Object) *ObjectAttrs {
|
|||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
acl := make([]ACLRule, len(o.Acl))
|
||||
for i, rule := range o.Acl {
|
||||
acl[i] = ACLRule{
|
||||
Entity: ACLEntity(rule.Entity),
|
||||
Role: ACLRole(rule.Role),
|
||||
}
|
||||
}
|
||||
owner := ""
|
||||
if o.Owner != nil {
|
||||
owner = o.Owner.Entity
|
||||
|
@ -827,27 +804,31 @@ func newObject(o *raw.Object) *ObjectAttrs {
|
|||
sha256 = o.CustomerEncryption.KeySha256
|
||||
}
|
||||
return &ObjectAttrs{
|
||||
Bucket: o.Bucket,
|
||||
Name: o.Name,
|
||||
ContentType: o.ContentType,
|
||||
ContentLanguage: o.ContentLanguage,
|
||||
CacheControl: o.CacheControl,
|
||||
ACL: acl,
|
||||
Owner: owner,
|
||||
ContentEncoding: o.ContentEncoding,
|
||||
ContentDisposition: o.ContentDisposition,
|
||||
Size: int64(o.Size),
|
||||
MD5: md5,
|
||||
CRC32C: crc32c,
|
||||
MediaLink: o.MediaLink,
|
||||
Metadata: o.Metadata,
|
||||
Generation: o.Generation,
|
||||
Metageneration: o.Metageneration,
|
||||
StorageClass: o.StorageClass,
|
||||
CustomerKeySHA256: sha256,
|
||||
Created: convertTime(o.TimeCreated),
|
||||
Deleted: convertTime(o.TimeDeleted),
|
||||
Updated: convertTime(o.Updated),
|
||||
Bucket: o.Bucket,
|
||||
Name: o.Name,
|
||||
ContentType: o.ContentType,
|
||||
ContentLanguage: o.ContentLanguage,
|
||||
CacheControl: o.CacheControl,
|
||||
EventBasedHold: o.EventBasedHold,
|
||||
TemporaryHold: o.TemporaryHold,
|
||||
RetentionExpirationTime: convertTime(o.RetentionExpirationTime),
|
||||
ACL: toObjectACLRules(o.Acl),
|
||||
Owner: owner,
|
||||
ContentEncoding: o.ContentEncoding,
|
||||
ContentDisposition: o.ContentDisposition,
|
||||
Size: int64(o.Size),
|
||||
MD5: md5,
|
||||
CRC32C: crc32c,
|
||||
MediaLink: o.MediaLink,
|
||||
Metadata: o.Metadata,
|
||||
Generation: o.Generation,
|
||||
Metageneration: o.Metageneration,
|
||||
StorageClass: o.StorageClass,
|
||||
CustomerKeySHA256: sha256,
|
||||
KMSKeyName: o.KmsKeyName,
|
||||
Created: convertTime(o.TimeCreated),
|
||||
Deleted: convertTime(o.TimeDeleted),
|
||||
Updated: convertTime(o.Updated),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -890,17 +871,6 @@ type Query struct {
|
|||
Versions bool
|
||||
}
|
||||
|
||||
// contentTyper implements ContentTyper to enable an
|
||||
// io.ReadCloser to specify its MIME type.
|
||||
type contentTyper struct {
|
||||
io.Reader
|
||||
t string
|
||||
}
|
||||
|
||||
func (c *contentTyper) ContentType() string {
|
||||
return c.t
|
||||
}
|
||||
|
||||
// Conditions constrain methods to act on specific generations of
|
||||
// objects.
|
||||
//
|
||||
|
@ -1130,4 +1100,12 @@ func setEncryptionHeaders(headers http.Header, key []byte, copySource bool) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
// TODO(jbd): Add storage.objects.watch.
|
||||
// ServiceAccount fetches the email address of the given project's Google Cloud Storage service account.
|
||||
func (c *Client) ServiceAccount(ctx context.Context, projectID string) (string, error) {
|
||||
r := c.raw.Projects.ServiceAccount.Get(projectID)
|
||||
res, err := r.Context(ctx).Do()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return res.EmailAddress, nil
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -15,13 +15,14 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/googleapi"
|
||||
raw "google.golang.org/api/storage/v1"
|
||||
)
|
||||
|
@ -47,8 +48,11 @@ type Writer struct {
|
|||
// to the nearest multiple of 256K. If zero, chunking will be disabled and
|
||||
// the object will be uploaded in a single request.
|
||||
//
|
||||
// ChunkSize will default to a reasonable value. Any custom configuration
|
||||
// must be done before the first Write call.
|
||||
// ChunkSize will default to a reasonable value. If you perform many concurrent
|
||||
// writes of small objects, you may wish set ChunkSize to a value that matches
|
||||
// your objects' sizes to avoid consuming large amounts of memory.
|
||||
//
|
||||
// ChunkSize must be set before the first Write call.
|
||||
ChunkSize int
|
||||
|
||||
// ProgressFunc can be used to monitor the progress of a large write.
|
||||
|
@ -68,8 +72,10 @@ type Writer struct {
|
|||
pw *io.PipeWriter
|
||||
|
||||
donec chan struct{} // closed after err and obj are set.
|
||||
err error
|
||||
obj *ObjectAttrs
|
||||
|
||||
mu sync.Mutex
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *Writer) open() error {
|
||||
|
@ -82,10 +88,15 @@ func (w *Writer) open() error {
|
|||
if !utf8.ValidString(attrs.Name) {
|
||||
return fmt.Errorf("storage: object name %q is not valid UTF-8", attrs.Name)
|
||||
}
|
||||
if attrs.KMSKeyName != "" && w.o.encryptionKey != nil {
|
||||
return errors.New("storage: cannot use KMSKeyName with a customer-supplied encryption key")
|
||||
}
|
||||
pr, pw := io.Pipe()
|
||||
w.pw = pw
|
||||
w.opened = true
|
||||
|
||||
go w.monitorCancel()
|
||||
|
||||
if w.ChunkSize < 0 {
|
||||
return errors.New("storage: Writer.ChunkSize must be non-negative")
|
||||
}
|
||||
|
@ -113,9 +124,17 @@ func (w *Writer) open() error {
|
|||
if w.ProgressFunc != nil {
|
||||
call.ProgressUpdater(func(n, _ int64) { w.ProgressFunc(n) })
|
||||
}
|
||||
if attrs.KMSKeyName != "" {
|
||||
call.KmsKeyName(attrs.KMSKeyName)
|
||||
}
|
||||
if attrs.PredefinedACL != "" {
|
||||
call.PredefinedAcl(attrs.PredefinedACL)
|
||||
}
|
||||
if err := setEncryptionHeaders(call.Header(), w.o.encryptionKey, false); err != nil {
|
||||
w.mu.Lock()
|
||||
w.err = err
|
||||
pr.CloseWithError(w.err)
|
||||
w.mu.Unlock()
|
||||
pr.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
var resp *raw.Object
|
||||
|
@ -142,8 +161,10 @@ func (w *Writer) open() error {
|
|||
}
|
||||
}
|
||||
if err != nil {
|
||||
w.mu.Lock()
|
||||
w.err = err
|
||||
pr.CloseWithError(w.err)
|
||||
w.mu.Unlock()
|
||||
pr.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
w.obj = newObject(resp)
|
||||
|
@ -158,15 +179,30 @@ func (w *Writer) open() error {
|
|||
// use the error returned from Writer.Close to determine if
|
||||
// the upload was successful.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
if w.err != nil {
|
||||
return 0, w.err
|
||||
w.mu.Lock()
|
||||
werr := w.err
|
||||
w.mu.Unlock()
|
||||
if werr != nil {
|
||||
return 0, werr
|
||||
}
|
||||
if !w.opened {
|
||||
if err := w.open(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return w.pw.Write(p)
|
||||
n, err = w.pw.Write(p)
|
||||
if err != nil {
|
||||
w.mu.Lock()
|
||||
werr := w.err
|
||||
w.mu.Unlock()
|
||||
// Preserve existing functionality that when context is canceled, Write will return
|
||||
// context.Canceled instead of "io: read/write on closed pipe". This hides the
|
||||
// pipe implementation detail from users and makes Write seem as though it's an RPC.
|
||||
if werr == context.Canceled || werr == context.DeadlineExceeded {
|
||||
return n, werr
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close completes the write operation and flushes any buffered data.
|
||||
|
@ -178,15 +214,39 @@ func (w *Writer) Close() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Closing either the read or write causes the entire pipe to close.
|
||||
if err := w.pw.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
<-w.donec
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.err
|
||||
}
|
||||
|
||||
// monitorCancel is intended to be used as a background goroutine. It monitors the
|
||||
// the context, and when it observes that the context has been canceled, it manually
|
||||
// closes things that do not take a context.
|
||||
func (w *Writer) monitorCancel() {
|
||||
select {
|
||||
case <-w.ctx.Done():
|
||||
w.mu.Lock()
|
||||
werr := w.ctx.Err()
|
||||
w.err = werr
|
||||
w.mu.Unlock()
|
||||
|
||||
// Closing either the read or write causes the entire pipe to close.
|
||||
w.CloseWithError(werr)
|
||||
case <-w.donec:
|
||||
}
|
||||
}
|
||||
|
||||
// CloseWithError aborts the write operation with the provided error.
|
||||
// CloseWithError always returns nil.
|
||||
//
|
||||
// Deprecated: cancel the context passed to NewWriter instead.
|
||||
func (w *Writer) CloseWithError(err error) error {
|
||||
if !w.opened {
|
||||
return nil
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
*.cover
|
|
@ -1,15 +0,0 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.6
|
||||
- 1.7
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
script:
|
||||
- gofmt -l .
|
||||
- goimports -l .
|
||||
- go tool vet .
|
||||
- go test -coverprofile=coverage.txt -covermode=atomic
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
|
@ -1,27 +0,0 @@
|
|||
Want to contribute? Great! First, read this page (including the small print at the end).
|
||||
|
||||
### Before you contribute
|
||||
Before we can use your code, you must sign the
|
||||
[Google Individual Contributor License Agreement]
|
||||
(https://cla.developers.google.com/about/google-individual)
|
||||
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||
copyright to your changes, even after your contribution becomes part of our
|
||||
codebase, so we need your permission to use and distribute your code. We also
|
||||
need to be sure of various other things—for instance that you'll tell us if you
|
||||
know that your code infringes on other people's patents. You don't have to sign
|
||||
the CLA until after you've submitted your code for review and a member has
|
||||
approved it, but you must do it before we can put your code into our codebase.
|
||||
Before you start working on a larger contribution, you should get in touch with
|
||||
us first through the issue tracker with your idea so that we can help out and
|
||||
possibly guide you. Coordinating up front makes it much easier to avoid
|
||||
frustration later on.
|
||||
|
||||
### Code reviews
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use Github pull requests for this purpose.
|
||||
|
||||
### The small print
|
||||
Contributions made by corporations are covered by a different agreement than
|
||||
the one above, the
|
||||
[Software Grant and Corporate Contributor License Agreement]
|
||||
(https://cla.developers.google.com/about/google-corporate).
|
|
@ -1,24 +0,0 @@
|
|||
Google API Extensions for Go
|
||||
============================
|
||||
|
||||
[![Build Status](https://travis-ci.org/googleapis/gax-go.svg?branch=master)](https://travis-ci.org/googleapis/gax-go)
|
||||
[![Code Coverage](https://img.shields.io/codecov/c/github/googleapis/gax-go.svg)](https://codecov.io/github/googleapis/gax-go)
|
||||
|
||||
Google API Extensions for Go (gax-go) is a set of modules which aids the
|
||||
development of APIs for clients and servers based on `gRPC` and Google API
|
||||
conventions.
|
||||
|
||||
Application code will rarely need to use this library directly,
|
||||
but the code generated automatically from API definition files can use it
|
||||
to simplify code generation and to provide more convenient and idiomatic API surface.
|
||||
|
||||
**This project is currently experimental and not supported.**
|
||||
|
||||
Go Versions
|
||||
===========
|
||||
This library requires Go 1.6 or above.
|
||||
|
||||
License
|
||||
=======
|
||||
BSD - please see [LICENSE](https://github.com/googleapis/gax-go/blob/master/LICENSE)
|
||||
for more information.
|
|
@ -1,176 +0,0 @@
|
|||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type matcher interface {
|
||||
match([]string) (int, error)
|
||||
String() string
|
||||
}
|
||||
|
||||
type segment struct {
|
||||
matcher
|
||||
name string
|
||||
}
|
||||
|
||||
type labelMatcher string
|
||||
|
||||
func (ls labelMatcher) match(segments []string) (int, error) {
|
||||
if len(segments) == 0 {
|
||||
return 0, fmt.Errorf("expected %s but no more segments found", ls)
|
||||
}
|
||||
if segments[0] != string(ls) {
|
||||
return 0, fmt.Errorf("expected %s but got %s", ls, segments[0])
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (ls labelMatcher) String() string {
|
||||
return string(ls)
|
||||
}
|
||||
|
||||
type wildcardMatcher int
|
||||
|
||||
func (wm wildcardMatcher) match(segments []string) (int, error) {
|
||||
if len(segments) == 0 {
|
||||
return 0, errors.New("no more segments found")
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (wm wildcardMatcher) String() string {
|
||||
return "*"
|
||||
}
|
||||
|
||||
type pathWildcardMatcher int
|
||||
|
||||
func (pwm pathWildcardMatcher) match(segments []string) (int, error) {
|
||||
length := len(segments) - int(pwm)
|
||||
if length <= 0 {
|
||||
return 0, errors.New("not sufficient segments are supplied for path wildcard")
|
||||
}
|
||||
return length, nil
|
||||
}
|
||||
|
||||
func (pwm pathWildcardMatcher) String() string {
|
||||
return "**"
|
||||
}
|
||||
|
||||
type ParseError struct {
|
||||
Pos int
|
||||
Template string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (pe ParseError) Error() string {
|
||||
return fmt.Sprintf("at %d of template '%s', %s", pe.Pos, pe.Template, pe.Message)
|
||||
}
|
||||
|
||||
// PathTemplate manages the template to build and match with paths used
|
||||
// by API services. It holds a template and variable names in it, and
|
||||
// it can extract matched patterns from a path string or build a path
|
||||
// string from a binding.
|
||||
//
|
||||
// See http.proto in github.com/googleapis/googleapis/ for the details of
|
||||
// the template syntax.
|
||||
type PathTemplate struct {
|
||||
segments []segment
|
||||
}
|
||||
|
||||
// NewPathTemplate parses a path template, and returns a PathTemplate
|
||||
// instance if successful.
|
||||
func NewPathTemplate(template string) (*PathTemplate, error) {
|
||||
return parsePathTemplate(template)
|
||||
}
|
||||
|
||||
// MustCompilePathTemplate is like NewPathTemplate but panics if the
|
||||
// expression cannot be parsed. It simplifies safe initialization of
|
||||
// global variables holding compiled regular expressions.
|
||||
func MustCompilePathTemplate(template string) *PathTemplate {
|
||||
pt, err := NewPathTemplate(template)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pt
|
||||
}
|
||||
|
||||
// Match attempts to match the given path with the template, and returns
|
||||
// the mapping of the variable name to the matched pattern string.
|
||||
func (pt *PathTemplate) Match(path string) (map[string]string, error) {
|
||||
paths := strings.Split(path, "/")
|
||||
values := map[string]string{}
|
||||
for _, segment := range pt.segments {
|
||||
length, err := segment.match(paths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if segment.name != "" {
|
||||
value := strings.Join(paths[:length], "/")
|
||||
if oldValue, ok := values[segment.name]; ok {
|
||||
values[segment.name] = oldValue + "/" + value
|
||||
} else {
|
||||
values[segment.name] = value
|
||||
}
|
||||
}
|
||||
paths = paths[length:]
|
||||
}
|
||||
if len(paths) != 0 {
|
||||
return nil, fmt.Errorf("Trailing path %s remains after the matching", strings.Join(paths, "/"))
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// Render creates a path string from its template and the binding from
|
||||
// the variable name to the value.
|
||||
func (pt *PathTemplate) Render(binding map[string]string) (string, error) {
|
||||
result := make([]string, 0, len(pt.segments))
|
||||
var lastVariableName string
|
||||
for _, segment := range pt.segments {
|
||||
name := segment.name
|
||||
if lastVariableName != "" && name == lastVariableName {
|
||||
continue
|
||||
}
|
||||
lastVariableName = name
|
||||
if name == "" {
|
||||
result = append(result, segment.String())
|
||||
} else if value, ok := binding[name]; ok {
|
||||
result = append(result, value)
|
||||
} else {
|
||||
return "", fmt.Errorf("%s is not found", name)
|
||||
}
|
||||
}
|
||||
built := strings.Join(result, "/")
|
||||
return built, nil
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This parser follows the syntax of path templates, from
|
||||
// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto.
|
||||
// The differences are that there is no custom verb, we allow the initial slash
|
||||
// to be absent, and that we are not strict as
|
||||
// https://tools.ietf.org/html/rfc6570 about the characters in identifiers and
|
||||
// literals.
|
||||
|
||||
type pathTemplateParser struct {
|
||||
r *strings.Reader
|
||||
runeCount int // the number of the current rune in the original string
|
||||
nextVar int // the number to use for the next unnamed variable
|
||||
seenName map[string]bool // names we've seen already
|
||||
seenPathWildcard bool // have we seen "**" already?
|
||||
}
|
||||
|
||||
func parsePathTemplate(template string) (pt *PathTemplate, err error) {
|
||||
p := &pathTemplateParser{
|
||||
r: strings.NewReader(template),
|
||||
seenName: map[string]bool{},
|
||||
}
|
||||
|
||||
// Handle panics with strings like errors.
|
||||
// See pathTemplateParser.error, below.
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
errmsg, ok := x.(errString)
|
||||
if !ok {
|
||||
panic(x)
|
||||
}
|
||||
pt = nil
|
||||
err = ParseError{p.runeCount, template, string(errmsg)}
|
||||
}
|
||||
}()
|
||||
|
||||
segs := p.template()
|
||||
// If there is a path wildcard, set its length. We can't do this
|
||||
// until we know how many segments we've got all together.
|
||||
for i, seg := range segs {
|
||||
if _, ok := seg.matcher.(pathWildcardMatcher); ok {
|
||||
segs[i].matcher = pathWildcardMatcher(len(segs) - i - 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
return &PathTemplate{segments: segs}, nil
|
||||
|
||||
}
|
||||
|
||||
// Used to indicate errors "thrown" by this parser. We don't use string because
|
||||
// many parts of the standard library panic with strings.
|
||||
type errString string
|
||||
|
||||
// Terminates parsing immediately with an error.
|
||||
func (p *pathTemplateParser) error(msg string) {
|
||||
panic(errString(msg))
|
||||
}
|
||||
|
||||
// Template = [ "/" ] Segments
|
||||
func (p *pathTemplateParser) template() []segment {
|
||||
var segs []segment
|
||||
if p.consume('/') {
|
||||
// Initial '/' needs an initial empty matcher.
|
||||
segs = append(segs, segment{matcher: labelMatcher("")})
|
||||
}
|
||||
return append(segs, p.segments("")...)
|
||||
}
|
||||
|
||||
// Segments = Segment { "/" Segment }
|
||||
func (p *pathTemplateParser) segments(name string) []segment {
|
||||
var segs []segment
|
||||
for {
|
||||
subsegs := p.segment(name)
|
||||
segs = append(segs, subsegs...)
|
||||
if !p.consume('/') {
|
||||
break
|
||||
}
|
||||
}
|
||||
return segs
|
||||
}
|
||||
|
||||
// Segment = "*" | "**" | LITERAL | Variable
|
||||
func (p *pathTemplateParser) segment(name string) []segment {
|
||||
if p.consume('*') {
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("$%d", p.nextVar)
|
||||
p.nextVar++
|
||||
}
|
||||
if p.consume('*') {
|
||||
if p.seenPathWildcard {
|
||||
p.error("multiple '**' disallowed")
|
||||
}
|
||||
p.seenPathWildcard = true
|
||||
// We'll change 0 to the right number at the end.
|
||||
return []segment{{name: name, matcher: pathWildcardMatcher(0)}}
|
||||
}
|
||||
return []segment{{name: name, matcher: wildcardMatcher(0)}}
|
||||
}
|
||||
if p.consume('{') {
|
||||
if name != "" {
|
||||
p.error("recursive named bindings are not allowed")
|
||||
}
|
||||
return p.variable()
|
||||
}
|
||||
return []segment{{name: name, matcher: labelMatcher(p.literal())}}
|
||||
}
|
||||
|
||||
// Variable = "{" FieldPath [ "=" Segments ] "}"
|
||||
// "{" is already consumed.
|
||||
func (p *pathTemplateParser) variable() []segment {
|
||||
// Simplification: treat FieldPath as LITERAL, instead of IDENT { '.' IDENT }
|
||||
name := p.literal()
|
||||
if p.seenName[name] {
|
||||
p.error(name + " appears multiple times")
|
||||
}
|
||||
p.seenName[name] = true
|
||||
var segs []segment
|
||||
if p.consume('=') {
|
||||
segs = p.segments(name)
|
||||
} else {
|
||||
// "{var}" is equivalent to "{var=*}"
|
||||
segs = []segment{{name: name, matcher: wildcardMatcher(0)}}
|
||||
}
|
||||
if !p.consume('}') {
|
||||
p.error("expected '}'")
|
||||
}
|
||||
return segs
|
||||
}
|
||||
|
||||
// A literal is any sequence of characters other than a few special ones.
|
||||
// The list of stop characters is not quite the same as in the template RFC.
|
||||
func (p *pathTemplateParser) literal() string {
|
||||
lit := p.consumeUntil("/*}{=")
|
||||
if lit == "" {
|
||||
p.error("empty literal")
|
||||
}
|
||||
return lit
|
||||
}
|
||||
|
||||
// Read runes until EOF or one of the runes in stopRunes is encountered.
|
||||
// If the latter, unread the stop rune. Return the accumulated runes as a string.
|
||||
func (p *pathTemplateParser) consumeUntil(stopRunes string) string {
|
||||
var runes []rune
|
||||
for {
|
||||
r, ok := p.readRune()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if strings.IndexRune(stopRunes, r) >= 0 {
|
||||
p.unreadRune()
|
||||
break
|
||||
}
|
||||
runes = append(runes, r)
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// If the next rune is r, consume it and return true.
|
||||
// Otherwise, leave the input unchanged and return false.
|
||||
func (p *pathTemplateParser) consume(r rune) bool {
|
||||
rr, ok := p.readRune()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if r == rr {
|
||||
return true
|
||||
}
|
||||
p.unreadRune()
|
||||
return false
|
||||
}
|
||||
|
||||
// Read the next rune from the input. Return it.
|
||||
// The second return value is false at EOF.
|
||||
func (p *pathTemplateParser) readRune() (rune, bool) {
|
||||
r, _, err := p.r.ReadRune()
|
||||
if err == io.EOF {
|
||||
return r, false
|
||||
}
|
||||
if err != nil {
|
||||
p.error(err.Error())
|
||||
}
|
||||
p.runeCount++
|
||||
return r, true
|
||||
}
|
||||
|
||||
// Put the last rune that was read back on the input.
|
||||
func (p *pathTemplateParser) unreadRune() {
|
||||
if err := p.r.UnreadRune(); err != nil {
|
||||
p.error(err.Error())
|
||||
}
|
||||
p.runeCount--
|
||||
}
|
|
@ -35,6 +35,7 @@ import (
|
|||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// CallOption is an option used by Invoke to control behaviors of RPC calls.
|
||||
|
@ -80,7 +81,11 @@ type boRetryer struct {
|
|||
}
|
||||
|
||||
func (r *boRetryer) Retry(err error) (time.Duration, bool) {
|
||||
c := grpc.Code(err)
|
||||
st, ok := status.FromError(err)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
c := st.Code()
|
||||
for _, rc := range r.codes {
|
||||
if c == rc {
|
||||
return r.backoff.Pause(), true
|
||||
|
@ -108,6 +113,7 @@ type Backoff struct {
|
|||
cur time.Duration
|
||||
}
|
||||
|
||||
// Pause returns the next time.Duration that the caller should use to backoff.
|
||||
func (bo *Backoff) Pause() time.Duration {
|
||||
if bo.Initial == 0 {
|
||||
bo.Initial = time.Second
|
||||
|
@ -121,7 +127,11 @@ func (bo *Backoff) Pause() time.Duration {
|
|||
if bo.Multiplier < 1 {
|
||||
bo.Multiplier = 2
|
||||
}
|
||||
d := time.Duration(rand.Int63n(int64(bo.cur)))
|
||||
// Select a duration between 1ns and the current max. It might seem
|
||||
// counterintuitive to have so much jitter, but
|
||||
// https://www.awsarchitectureblog.com/2015/03/backoff.html argues that
|
||||
// that is the best strategy.
|
||||
d := time.Duration(1 + rand.Int63n(int64(bo.cur)))
|
||||
bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier)
|
||||
if bo.cur > bo.Max {
|
||||
bo.cur = bo.Max
|
||||
|
@ -129,8 +139,23 @@ func (bo *Backoff) Pause() time.Duration {
|
|||
return d
|
||||
}
|
||||
|
||||
type grpcOpt []grpc.CallOption
|
||||
|
||||
func (o grpcOpt) Resolve(s *CallSettings) {
|
||||
s.GRPC = o
|
||||
}
|
||||
|
||||
// WithGRPCOptions allows passing gRPC call options during client creation.
|
||||
func WithGRPCOptions(opt ...grpc.CallOption) CallOption {
|
||||
return grpcOpt(append([]grpc.CallOption(nil), opt...))
|
||||
}
|
||||
|
||||
// CallSettings allow fine-grained control over how calls are made.
|
||||
type CallSettings struct {
|
||||
// Retry returns a Retryer to be used to control retry logic of a method call.
|
||||
// If Retry is nil or the returned Retryer is nil, the call will not be retried.
|
||||
Retry func() Retryer
|
||||
|
||||
// CallOptions to be forwarded to GRPC.
|
||||
GRPC []grpc.CallOption
|
||||
}
|
|
@ -33,8 +33,7 @@
|
|||
// Application code will rarely need to use this library directly.
|
||||
// However, code generated automatically from API definition files can use it
|
||||
// to simplify code generation and to provide more convenient and idiomatic API surfaces.
|
||||
//
|
||||
// This project is currently experimental and not supported.
|
||||
package gax
|
||||
|
||||
const Version = "0.1.0"
|
||||
// Version specifies the gax-go version being used.
|
||||
const Version = "2.0.3"
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/googleapis/gax-go/v2
|
||||
|
||||
require google.golang.org/grpc v1.16.0
|
|
@ -0,0 +1,26 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2018, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import "bytes"
|
||||
|
||||
// XGoogHeader is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// XGoogHeader formats key-value pairs.
|
||||
// The resulting string is suitable for x-goog-api-client header.
|
||||
func XGoogHeader(keyval ...string) string {
|
||||
if len(keyval) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(keyval)%2 != 0 {
|
||||
panic("gax.Header: odd argument count")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(keyval); i += 2 {
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(keyval[i])
|
||||
buf.WriteByte('/')
|
||||
buf.WriteString(keyval[i+1])
|
||||
}
|
||||
return buf.String()[1:]
|
||||
}
|
|
@ -30,13 +30,13 @@
|
|||
package gax
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// A user defined call stub.
|
||||
type APICall func(context.Context) error
|
||||
// APICall is a user defined call stub.
|
||||
type APICall func(context.Context, CallSettings) error
|
||||
|
||||
// Invoke calls the given APICall,
|
||||
// performing retries as specified by opts, if any.
|
||||
|
@ -67,13 +67,22 @@ type sleeper func(ctx context.Context, d time.Duration) error
|
|||
func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error {
|
||||
var retryer Retryer
|
||||
for {
|
||||
err := call(ctx)
|
||||
err := call(ctx, settings)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if settings.Retry == nil {
|
||||
return err
|
||||
}
|
||||
// Never retry permanent certificate errors. (e.x. if ca-certificates
|
||||
// are not installed). We should only make very few, targeted
|
||||
// exceptions: many (other) status=Unavailable should be retried, such
|
||||
// as if there's a network hiccup, or the internet goes out for a
|
||||
// minute. This is also why here we are doing string parsing instead of
|
||||
// simply making Unavailable a non-retried code elsewhere.
|
||||
if strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
|
||||
return err
|
||||
}
|
||||
if retryer == nil {
|
||||
if r := settings.Retry(); r != nil {
|
||||
retryer = r
|
|
@ -2,7 +2,7 @@ language: go
|
|||
|
||||
go:
|
||||
# 1.8 is tested by AppVeyor
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
|
||||
go_import_path: go.opencensus.io
|
||||
|
||||
|
@ -18,10 +18,10 @@ before_script:
|
|||
|
||||
script:
|
||||
- embedmd -d README.md # Ensure embedded code is up-to-date
|
||||
- dep ensure -v
|
||||
- go build ./... # Ensure dependency updates don't break build
|
||||
- if [ -n "$(gofmt -s -l $GO_FILES)" ]; then echo "gofmt the following files:"; gofmt -s -l $GO_FILES; exit 1; fi
|
||||
- go vet ./...
|
||||
- go test -v -race $PKGS # Run all the tests with the race detector enabled
|
||||
- GOARCH=386 go test -v $PKGS # Run all tests against a 386 architecture
|
||||
- 'if [[ $TRAVIS_GO_VERSION = 1.8* ]]; then ! golint ./... | grep -vE "(_mock|_string|\.pb)\.go:"; fi'
|
||||
- go run internal/check/version.go
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
OpenCensus Go is a Go implementation of OpenCensus, a toolkit for
|
||||
collecting application performance and behavior monitoring data.
|
||||
Currently it consists of three major components: tags, stats, and tracing.
|
||||
Currently it consists of three major components: tags, stats and tracing.
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -38,7 +38,7 @@ integration with your RPC framework:
|
|||
* [Redis goredis/redis](https://godoc.org/github.com/orijtech/redis)
|
||||
* [Memcache](https://godoc.org/github.com/orijtech/gomemcache)
|
||||
|
||||
If you're a framework not listed here, you could either implement your own middleware for your
|
||||
If you're using a framework not listed here, you could either implement your own middleware for your
|
||||
framework or use [custom stats](#stats) and [spans](#spans) directly in your application.
|
||||
|
||||
## Exporters
|
||||
|
@ -56,6 +56,7 @@ can implement their own exporters by implementing the exporter interfaces
|
|||
* [AWS X-Ray][exporter-xray] for traces
|
||||
* [Datadog][exporter-datadog] for stats and traces
|
||||
* [Graphite][exporter-graphite] for stats
|
||||
* [Honeycomb][exporter-honeycomb] for traces
|
||||
|
||||
## Overview
|
||||
|
||||
|
@ -73,7 +74,7 @@ in the same process or can be encoded to be transmitted on the wire. Usually, th
|
|||
be handled by an integration plugin, e.g. `ocgrpc.ServerHandler` and `ocgrpc.ClientHandler`
|
||||
for gRPC.
|
||||
|
||||
Package tag allows adding or modifying tags in the current context.
|
||||
Package `tag` allows adding or modifying tags in the current context.
|
||||
|
||||
[embedmd]:# (internal/readme/tags.go new)
|
||||
```go
|
||||
|
@ -177,8 +178,8 @@ Spans can have parents or can be root spans if they don't have any parents.
|
|||
The current span is propagated in-process and across the network to allow associating
|
||||
new child spans with the parent.
|
||||
|
||||
In the same process, context.Context is used to propagate spans.
|
||||
trace.StartSpan creates a new span as a root if the current context
|
||||
In the same process, `context.Context` is used to propagate spans.
|
||||
`trace.StartSpan` creates a new span as a root if the current context
|
||||
doesn't contain a span. Or, it creates a child of the span that is
|
||||
already in current context. The returned context can be used to keep
|
||||
propagating the newly created span in the current context.
|
||||
|
@ -194,8 +195,8 @@ defer span.End()
|
|||
Across the network, OpenCensus provides different propagation
|
||||
methods for different protocols.
|
||||
|
||||
* gRPC integrations uses the OpenCensus' [binary propagation format](https://godoc.org/go.opencensus.io/trace/propagation).
|
||||
* HTTP integrations uses Zipkin's [B3](https://github.com/openzipkin/b3-propagation)
|
||||
* gRPC integrations use the OpenCensus' [binary propagation format](https://godoc.org/go.opencensus.io/trace/propagation).
|
||||
* HTTP integrations use Zipkin's [B3](https://github.com/openzipkin/b3-propagation)
|
||||
by default but can be configured to use a custom propagation method by setting another
|
||||
[propagation.HTTPFormat](https://godoc.org/go.opencensus.io/trace/propagation#HTTPFormat).
|
||||
|
||||
|
@ -259,4 +260,4 @@ release in which the functionality was marked *Deprecated*.
|
|||
[exporter-xray]: https://github.com/census-ecosystem/opencensus-go-exporter-aws
|
||||
[exporter-datadog]: https://github.com/DataDog/opencensus-go-exporter-datadog
|
||||
[exporter-graphite]: https://github.com/census-ecosystem/opencensus-go-exporter-graphite
|
||||
|
||||
[exporter-honeycomb]: https://github.com/honeycombio/opencensus-exporter
|
||||
|
|
|
@ -5,8 +5,10 @@ platform: x64
|
|||
clone_folder: c:\gopath\src\go.opencensus.io
|
||||
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
GOVERSION: 1.8
|
||||
GOPATH: 'c:\gopath'
|
||||
GOVERSION: '1.11'
|
||||
GO111MODULE: 'on'
|
||||
CGO_ENABLED: '0' # See: https://github.com/appveyor/ci/issues/2613
|
||||
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
|
@ -18,7 +20,5 @@ deploy: false
|
|||
|
||||
test_script:
|
||||
- cd %APPVEYOR_BUILD_FOLDER%
|
||||
- gofmt -w .
|
||||
- go get -v -t .\...
|
||||
- go test -race -v .\...
|
||||
- go vet .\...
|
||||
- go build -v .\...
|
||||
- go test -v .\... # No -race because cgo is disabled
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package exemplar implements support for exemplars. Exemplars are additional
|
||||
// data associated with each measurement.
|
||||
//
|
||||
// Their purpose it to provide an example of the kind of thing
|
||||
// (request, RPC, trace span, etc.) that resulted in that measurement.
|
||||
package exemplar
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyTraceID = "trace_id"
|
||||
KeySpanID = "span_id"
|
||||
KeyPrefixTag = "tag:"
|
||||
)
|
||||
|
||||
// Exemplar is an example data point associated with each bucket of a
|
||||
// distribution type aggregation.
|
||||
type Exemplar struct {
|
||||
Value float64 // the value that was recorded
|
||||
Timestamp time.Time // the time the value was recorded
|
||||
Attachments Attachments // attachments (if any)
|
||||
}
|
||||
|
||||
// Attachments is a map of extra values associated with a recorded data point.
|
||||
// The map should only be mutated from AttachmentExtractor functions.
|
||||
type Attachments map[string]string
|
||||
|
||||
// AttachmentExtractor is a function capable of extracting exemplar attachments
|
||||
// from the context used to record measurements.
|
||||
// The map passed to the function should be mutated and returned. It will
|
||||
// initially be nil: the first AttachmentExtractor that would like to add keys to the
|
||||
// map is responsible for initializing it.
|
||||
type AttachmentExtractor func(ctx context.Context, a Attachments) Attachments
|
||||
|
||||
var extractors []AttachmentExtractor
|
||||
|
||||
// RegisterAttachmentExtractor registers the given extractor associated with the exemplar
|
||||
// type name.
|
||||
//
|
||||
// Extractors will be used to attempt to extract exemplars from the context
|
||||
// associated with each recorded measurement.
|
||||
//
|
||||
// Packages that support exemplars should register their extractor functions on
|
||||
// initialization.
|
||||
//
|
||||
// RegisterAttachmentExtractor should not be called after any measurements have
|
||||
// been recorded.
|
||||
func RegisterAttachmentExtractor(e AttachmentExtractor) {
|
||||
extractors = append(extractors, e)
|
||||
}
|
||||
|
||||
// NewFromContext extracts exemplars from the given context.
|
||||
// Each registered AttachmentExtractor (see RegisterAttachmentExtractor) is called in an
|
||||
// unspecified order to add attachments to the exemplar.
|
||||
func AttachmentsFromContext(ctx context.Context) Attachments {
|
||||
var a Attachments
|
||||
for _, extractor := range extractors {
|
||||
a = extractor(ctx, a)
|
||||
}
|
||||
return a
|
||||
}
|
|
@ -3,7 +3,11 @@ module go.opencensus.io
|
|||
require (
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
|
||||
github.com/golang/protobuf v1.2.0
|
||||
github.com/google/go-cmp v0.2.0
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||
github.com/openzipkin/zipkin-go v0.1.1
|
||||
github.com/prometheus/client_golang v0.8.0
|
||||
|
@ -17,4 +21,5 @@ require (
|
|||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b
|
||||
google.golang.org/grpc v1.14.0
|
||||
gopkg.in/yaml.v2 v2.2.1 // indirect
|
||||
)
|
||||
|
|
|
@ -1,21 +1,48 @@
|
|||
git.apache.org/thrift.git v0.0.0-20180807212849-6e67faa92827/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999 h1:sihTnRgTOUSCQz0iS0pjZuFQy/z7GXCJgSBg3+rZKHw=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0 h1:WcmKMm43DR7RdtlkEXQJyo5ws8iTp98CyhCCbOHMvNI=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/openzipkin/zipkin-go v0.1.1 h1:A/ADD6HaPnAKj3yS7HjGHRK77qi41Hi0DirOOIQAeIw=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
golang.org/x/net v0.0.0-20180821023952-922f4815f713/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180821140842-3b58ed4ad339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/api v0.0.0-20180818000503-e21acd801f91/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf h1:rjxqQmxjyqerRKEj+tZW+MCm4LgpFXu18bsEoCMgDsk=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.14.0 h1:ArxJuB1NWfPY6r9Gp9gqwplT0Ge7nqv9msgu03lHLmo=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -17,5 +17,5 @@ package opencensus // import "go.opencensus.io"
|
|||
|
||||
// Version is the current release version of OpenCensus in use.
|
||||
func Version() string {
|
||||
return "0.17.0"
|
||||
return "0.18.0"
|
||||
}
|
||||
|
|
|
@ -47,6 +47,10 @@ type Transport struct {
|
|||
// for spans started by this transport.
|
||||
StartOptions trace.StartOptions
|
||||
|
||||
// GetStartOptions allows to set start options per request. If set,
|
||||
// StartOptions is going to be ignored.
|
||||
GetStartOptions func(*http.Request) trace.StartOptions
|
||||
|
||||
// NameFromRequest holds the function to use for generating the span name
|
||||
// from the information found in the outgoing HTTP Request. By default the
|
||||
// name equals the URL Path.
|
||||
|
@ -75,11 +79,17 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||
if spanNameFormatter == nil {
|
||||
spanNameFormatter = spanNameFromURL
|
||||
}
|
||||
|
||||
startOpts := t.StartOptions
|
||||
if t.GetStartOptions != nil {
|
||||
startOpts = t.GetStartOptions(req)
|
||||
}
|
||||
|
||||
rt = &traceTransport{
|
||||
base: rt,
|
||||
format: format,
|
||||
startOptions: trace.StartOptions{
|
||||
Sampler: t.StartOptions.Sampler,
|
||||
Sampler: startOpts.Sampler,
|
||||
SpanKind: trace.SpanKindClient,
|
||||
},
|
||||
formatSpanName: spanNameFormatter,
|
||||
|
|
|
@ -34,8 +34,11 @@ type statsTransport struct {
|
|||
// RoundTrip implements http.RoundTripper, delegating to Base and recording stats for the request.
|
||||
func (t statsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
ctx, _ := tag.New(req.Context(),
|
||||
tag.Upsert(KeyClientHost, req.URL.Host),
|
||||
tag.Upsert(Host, req.URL.Host),
|
||||
tag.Upsert(KeyClientPath, req.URL.Path),
|
||||
tag.Upsert(Path, req.URL.Path),
|
||||
tag.Upsert(KeyClientMethod, req.Method),
|
||||
tag.Upsert(Method, req.Method))
|
||||
req = req.WithContext(ctx)
|
||||
track := &tracker{
|
||||
|
@ -92,15 +95,22 @@ var _ io.ReadCloser = (*tracker)(nil)
|
|||
|
||||
func (t *tracker) end() {
|
||||
t.endOnce.Do(func() {
|
||||
latencyMs := float64(time.Since(t.start)) / float64(time.Millisecond)
|
||||
m := []stats.Measurement{
|
||||
ClientLatency.M(float64(time.Since(t.start)) / float64(time.Millisecond)),
|
||||
ClientSentBytes.M(t.reqSize),
|
||||
ClientReceivedBytes.M(t.respSize),
|
||||
ClientRoundtripLatency.M(latencyMs),
|
||||
ClientLatency.M(latencyMs),
|
||||
ClientResponseBytes.M(t.respSize),
|
||||
}
|
||||
if t.reqSize >= 0 {
|
||||
m = append(m, ClientRequestBytes.M(t.reqSize))
|
||||
}
|
||||
ctx, _ := tag.New(t.ctx, tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)))
|
||||
stats.Record(ctx, m...)
|
||||
|
||||
stats.RecordWithTags(t.ctx, []tag.Mutator{
|
||||
tag.Upsert(StatusCode, strconv.Itoa(t.statusCode)),
|
||||
tag.Upsert(KeyClientStatus, strconv.Itoa(t.statusCode)),
|
||||
}, m...)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,10 @@ type Handler struct {
|
|||
// for spans started by this transport.
|
||||
StartOptions trace.StartOptions
|
||||
|
||||
// GetStartOptions allows to set start options per request. If set,
|
||||
// StartOptions is going to be ignored.
|
||||
GetStartOptions func(*http.Request) trace.StartOptions
|
||||
|
||||
// IsPublicEndpoint should be set to true for publicly accessible HTTP(S)
|
||||
// servers. If true, any trace metadata set on the incoming request will
|
||||
// be added as a linked trace instead of being added as a parent of the
|
||||
|
@ -93,15 +97,21 @@ func (h *Handler) startTrace(w http.ResponseWriter, r *http.Request) (*http.Requ
|
|||
name = h.FormatSpanName(r)
|
||||
}
|
||||
ctx := r.Context()
|
||||
|
||||
startOpts := h.StartOptions
|
||||
if h.GetStartOptions != nil {
|
||||
startOpts = h.GetStartOptions(r)
|
||||
}
|
||||
|
||||
var span *trace.Span
|
||||
sc, ok := h.extractSpanContext(r)
|
||||
if ok && !h.IsPublicEndpoint {
|
||||
ctx, span = trace.StartSpanWithRemoteParent(ctx, name, sc,
|
||||
trace.WithSampler(h.StartOptions.Sampler),
|
||||
trace.WithSampler(startOpts.Sampler),
|
||||
trace.WithSpanKind(trace.SpanKindServer))
|
||||
} else {
|
||||
ctx, span = trace.StartSpan(ctx, name,
|
||||
trace.WithSampler(h.StartOptions.Sampler),
|
||||
trace.WithSampler(startOpts.Sampler),
|
||||
trace.WithSpanKind(trace.SpanKindServer),
|
||||
)
|
||||
if ok {
|
||||
|
@ -168,6 +178,7 @@ func (t *trackingResponseWriter) end(tags *addedTags) {
|
|||
|
||||
span := trace.FromContext(t.ctx)
|
||||
span.SetStatus(TraceStatus(t.statusCode, t.statusLine))
|
||||
span.AddAttributes(trace.Int64Attribute(StatusCodeAttribute, int64(t.statusCode)))
|
||||
|
||||
m := []stats.Measurement{
|
||||
ServerLatency.M(float64(time.Since(t.start)) / float64(time.Millisecond)),
|
||||
|
@ -179,8 +190,7 @@ func (t *trackingResponseWriter) end(tags *addedTags) {
|
|||
allTags := make([]tag.Mutator, len(tags.t)+1)
|
||||
allTags[0] = tag.Upsert(StatusCode, strconv.Itoa(t.statusCode))
|
||||
copy(allTags[1:], tags.t)
|
||||
ctx, _ := tag.New(t.ctx, allTags...)
|
||||
stats.Record(ctx, m...)
|
||||
stats.RecordWithTags(t.ctx, allTags, m...)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ type spanAnnotator struct {
|
|||
|
||||
// TODO: Remove NewSpanAnnotator at the next release.
|
||||
|
||||
// NewSpanAnnotator returns a httptrace.ClientTrace which annotates
|
||||
// all emitted httptrace events on the provided Span.
|
||||
// Deprecated: Use NewSpanAnnotatingClientTrace instead
|
||||
func NewSpanAnnotator(r *http.Request, s *trace.Span) *httptrace.ClientTrace {
|
||||
return NewSpanAnnotatingClientTrace(r, s)
|
||||
|
|
|
@ -22,10 +22,33 @@ import (
|
|||
|
||||
// The following client HTTP measures are supported for use in custom views.
|
||||
var (
|
||||
ClientRequestCount = stats.Int64("opencensus.io/http/client/request_count", "Number of HTTP requests started", stats.UnitDimensionless)
|
||||
ClientRequestBytes = stats.Int64("opencensus.io/http/client/request_bytes", "HTTP request body size if set as ContentLength (uncompressed)", stats.UnitBytes)
|
||||
// Deprecated: Use a Count aggregation over one of the other client measures to achieve the same effect.
|
||||
ClientRequestCount = stats.Int64("opencensus.io/http/client/request_count", "Number of HTTP requests started", stats.UnitDimensionless)
|
||||
// Deprecated: Use ClientSentBytes.
|
||||
ClientRequestBytes = stats.Int64("opencensus.io/http/client/request_bytes", "HTTP request body size if set as ContentLength (uncompressed)", stats.UnitBytes)
|
||||
// Deprecated: Use ClientReceivedBytes.
|
||||
ClientResponseBytes = stats.Int64("opencensus.io/http/client/response_bytes", "HTTP response body size (uncompressed)", stats.UnitBytes)
|
||||
ClientLatency = stats.Float64("opencensus.io/http/client/latency", "End-to-end latency", stats.UnitMilliseconds)
|
||||
// Deprecated: Use ClientRoundtripLatency.
|
||||
ClientLatency = stats.Float64("opencensus.io/http/client/latency", "End-to-end latency", stats.UnitMilliseconds)
|
||||
)
|
||||
|
||||
// Client measures supported for use in custom views.
|
||||
var (
|
||||
ClientSentBytes = stats.Int64(
|
||||
"opencensus.io/http/client/sent_bytes",
|
||||
"Total bytes sent in request body (not including headers)",
|
||||
stats.UnitBytes,
|
||||
)
|
||||
ClientReceivedBytes = stats.Int64(
|
||||
"opencensus.io/http/client/received_bytes",
|
||||
"Total bytes received in response bodies (not including headers but including error responses with bodies)",
|
||||
stats.UnitBytes,
|
||||
)
|
||||
ClientRoundtripLatency = stats.Float64(
|
||||
"opencensus.io/http/client/roundtrip_latency",
|
||||
"Time between first byte of request headers sent to last byte of response received, or terminal error",
|
||||
stats.UnitMilliseconds,
|
||||
)
|
||||
)
|
||||
|
||||
// The following server HTTP measures are supported for use in custom views:
|
||||
|
@ -67,6 +90,18 @@ var (
|
|||
KeyServerRoute, _ = tag.NewKey("http_server_route")
|
||||
)
|
||||
|
||||
// Client tag keys.
|
||||
var (
|
||||
// KeyClientMethod is the HTTP method, capitalized (i.e. GET, POST, PUT, DELETE, etc.).
|
||||
KeyClientMethod, _ = tag.NewKey("http_client_method")
|
||||
// KeyClientPath is the URL path (not including query string).
|
||||
KeyClientPath, _ = tag.NewKey("http_client_path")
|
||||
// KeyClientStatus is the HTTP status code as an integer (e.g. 200, 404, 500.), or "error" if no response status line was received.
|
||||
KeyClientStatus, _ = tag.NewKey("http_client_status")
|
||||
// KeyClientHost is the value of the request Host header.
|
||||
KeyClientHost, _ = tag.NewKey("http_client_host")
|
||||
)
|
||||
|
||||
// Default distributions used by views in this package.
|
||||
var (
|
||||
DefaultSizeDistribution = view.Distribution(0, 1024, 2048, 4096, 16384, 65536, 262144, 1048576, 4194304, 16777216, 67108864, 268435456, 1073741824, 4294967296)
|
||||
|
@ -74,8 +109,43 @@ var (
|
|||
)
|
||||
|
||||
// Package ochttp provides some convenience views.
|
||||
// You need to register the views for data to actually be collected.
|
||||
// You still need to register these views for data to actually be collected.
|
||||
var (
|
||||
ClientSentBytesDistribution = &view.View{
|
||||
Name: "opencensus.io/http/client/sent_bytes",
|
||||
Measure: ClientSentBytes,
|
||||
Aggregation: DefaultSizeDistribution,
|
||||
Description: "Total bytes sent in request body (not including headers), by HTTP method and response status",
|
||||
TagKeys: []tag.Key{KeyClientMethod, KeyClientStatus},
|
||||
}
|
||||
|
||||
ClientReceivedBytesDistribution = &view.View{
|
||||
Name: "opencensus.io/http/client/received_bytes",
|
||||
Measure: ClientReceivedBytes,
|
||||
Aggregation: DefaultSizeDistribution,
|
||||
Description: "Total bytes received in response bodies (not including headers but including error responses with bodies), by HTTP method and response status",
|
||||
TagKeys: []tag.Key{KeyClientMethod, KeyClientStatus},
|
||||
}
|
||||
|
||||
ClientRoundtripLatencyDistribution = &view.View{
|
||||
Name: "opencensus.io/http/client/roundtrip_latency",
|
||||
Measure: ClientRoundtripLatency,
|
||||
Aggregation: DefaultLatencyDistribution,
|
||||
Description: "End-to-end latency, by HTTP method and response status",
|
||||
TagKeys: []tag.Key{KeyClientMethod, KeyClientStatus},
|
||||
}
|
||||
|
||||
ClientCompletedCount = &view.View{
|
||||
Name: "opencensus.io/http/client/completed_count",
|
||||
Measure: ClientRoundtripLatency,
|
||||
Aggregation: view.Count(),
|
||||
Description: "Count of completed requests, by HTTP method and response status",
|
||||
TagKeys: []tag.Key{KeyClientMethod, KeyClientStatus},
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// Deprecated: No direct replacement, but see ClientCompletedCount.
|
||||
ClientRequestCountView = &view.View{
|
||||
Name: "opencensus.io/http/client/request_count",
|
||||
Description: "Count of HTTP requests started",
|
||||
|
@ -83,43 +153,50 @@ var (
|
|||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
// Deprecated: Use ClientSentBytesDistribution.
|
||||
ClientRequestBytesView = &view.View{
|
||||
Name: "opencensus.io/http/client/request_bytes",
|
||||
Description: "Size distribution of HTTP request body",
|
||||
Measure: ClientRequestBytes,
|
||||
Measure: ClientSentBytes,
|
||||
Aggregation: DefaultSizeDistribution,
|
||||
}
|
||||
|
||||
// Deprecated: Use ClientReceivedBytesDistribution.
|
||||
ClientResponseBytesView = &view.View{
|
||||
Name: "opencensus.io/http/client/response_bytes",
|
||||
Description: "Size distribution of HTTP response body",
|
||||
Measure: ClientResponseBytes,
|
||||
Measure: ClientReceivedBytes,
|
||||
Aggregation: DefaultSizeDistribution,
|
||||
}
|
||||
|
||||
// Deprecated: Use ClientRoundtripLatencyDistribution.
|
||||
ClientLatencyView = &view.View{
|
||||
Name: "opencensus.io/http/client/latency",
|
||||
Description: "Latency distribution of HTTP requests",
|
||||
Measure: ClientLatency,
|
||||
Measure: ClientRoundtripLatency,
|
||||
Aggregation: DefaultLatencyDistribution,
|
||||
}
|
||||
|
||||
// Deprecated: Use ClientCompletedCount.
|
||||
ClientRequestCountByMethod = &view.View{
|
||||
Name: "opencensus.io/http/client/request_count_by_method",
|
||||
Description: "Client request count by HTTP method",
|
||||
TagKeys: []tag.Key{Method},
|
||||
Measure: ClientRequestCount,
|
||||
Measure: ClientSentBytes,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
|
||||
// Deprecated: Use ClientCompletedCount.
|
||||
ClientResponseCountByStatusCode = &view.View{
|
||||
Name: "opencensus.io/http/client/response_count_by_status_code",
|
||||
Description: "Client response count by status code",
|
||||
TagKeys: []tag.Key{StatusCode},
|
||||
Measure: ClientLatency,
|
||||
Measure: ClientRoundtripLatency,
|
||||
Aggregation: view.Count(),
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
ServerRequestCountView = &view.View{
|
||||
Name: "opencensus.io/http/server/request_count",
|
||||
Description: "Count of HTTP requests started",
|
||||
|
@ -166,6 +243,7 @@ var (
|
|||
)
|
||||
|
||||
// DefaultClientViews are the default client views provided by this package.
|
||||
// Deprecated: No replacement. Register the views you would like individually.
|
||||
var DefaultClientViews = []*view.View{
|
||||
ClientRequestCountView,
|
||||
ClientRequestBytesView,
|
||||
|
@ -176,6 +254,7 @@ var DefaultClientViews = []*view.View{
|
|||
}
|
||||
|
||||
// DefaultServerViews are the default server views provided by this package.
|
||||
// Deprecated: No replacement. Register the views you would like individually.
|
||||
var DefaultServerViews = []*view.View{
|
||||
ServerRequestCountView,
|
||||
ServerRequestBytesView,
|
||||
|
|
|
@ -21,35 +21,49 @@ aggregate the collected data, and export the aggregated data.
|
|||
|
||||
Measures
|
||||
|
||||
A measure represents a type of metric to be tracked and recorded.
|
||||
A measure represents a type of data point to be tracked and recorded.
|
||||
For example, latency, request Mb/s, and response Mb/s are measures
|
||||
to collect from a server.
|
||||
|
||||
Each measure needs to be registered before being used. Measure
|
||||
constructors such as Int64 and Float64 automatically
|
||||
Measure constructors such as Int64 and Float64 automatically
|
||||
register the measure by the given name. Each registered measure needs
|
||||
to be unique by name. Measures also have a description and a unit.
|
||||
|
||||
Libraries can define and export measures for their end users to
|
||||
create views and collect instrumentation data.
|
||||
Libraries can define and export measures. Application authors can then
|
||||
create views and collect and break down measures by the tags they are
|
||||
interested in.
|
||||
|
||||
Recording measurements
|
||||
|
||||
Measurement is a data point to be collected for a measure. For example,
|
||||
for a latency (ms) measure, 100 is a measurement that represents a 100ms
|
||||
latency event. Users collect data points on the existing measures with
|
||||
latency event. Measurements are created from measures with
|
||||
the current context. Tags from the current context are recorded with the
|
||||
measurements if they are any.
|
||||
|
||||
Recorded measurements are dropped immediately if user is not aggregating
|
||||
them via views. Users don't necessarily need to conditionally enable/disable
|
||||
Recorded measurements are dropped immediately if no views are registered for them.
|
||||
There is usually no need to conditionally enable and disable
|
||||
recording to reduce cost. Recording of measurements is cheap.
|
||||
|
||||
Libraries can always record measurements, and end-users can later decide
|
||||
Libraries can always record measurements, and applications can later decide
|
||||
on which measurements they want to collect by registering views. This allows
|
||||
libraries to turn on the instrumentation by default.
|
||||
|
||||
Exemplars
|
||||
|
||||
For a given recorded measurement, the associated exemplar is a diagnostic map
|
||||
that gives more information about the measurement.
|
||||
|
||||
When aggregated using a Distribution aggregation, an exemplar is kept for each
|
||||
bucket in the Distribution. This allows you to easily find an example of a
|
||||
measurement that fell into each bucket.
|
||||
|
||||
For example, if you also use the OpenCensus trace package and you
|
||||
record a measurement with a context that contains a sampled trace span,
|
||||
then the trace span will be added to the exemplar associated with the measurement.
|
||||
|
||||
When exported to a supporting back end, you should be able to easily navigate
|
||||
to example traces that fell into each bucket in the Distribution.
|
||||
|
||||
*/
|
||||
package stats // import "go.opencensus.io/stats"
|
||||
|
||||
// TODO(acetechnologist): Add a link to the language independent OpenCensus
|
||||
// spec when it is available.
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
// DefaultRecorder will be called for each Record call.
|
||||
var DefaultRecorder func(*tag.Map, interface{})
|
||||
var DefaultRecorder func(tags *tag.Map, measurement interface{}, attachments map[string]string)
|
||||
|
||||
// SubscriptionReporter reports when a view subscribed with a measure.
|
||||
var SubscriptionReporter func(measure string)
|
||||
|
|
|
@ -68,6 +68,21 @@ func (m *measureDescriptor) subscribed() bool {
|
|||
return atomic.LoadInt32(&m.subs) == 1
|
||||
}
|
||||
|
||||
// Name returns the name of the measure.
|
||||
func (m *measureDescriptor) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
// Description returns the description of the measure.
|
||||
func (m *measureDescriptor) Description() string {
|
||||
return m.description
|
||||
}
|
||||
|
||||
// Unit returns the unit of the measure.
|
||||
func (m *measureDescriptor) Unit() string {
|
||||
return m.unit
|
||||
}
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
measures = make(map[string]*measureDescriptor)
|
||||
|
@ -94,7 +109,7 @@ func registerMeasureHandle(name, desc, unit string) *measureDescriptor {
|
|||
// provides M to convert an int64 into a measurement.
|
||||
type Measurement struct {
|
||||
v float64
|
||||
m Measure
|
||||
m *measureDescriptor
|
||||
}
|
||||
|
||||
// Value returns the value of the Measurement as a float64.
|
||||
|
|
|
@ -17,31 +17,13 @@ package stats
|
|||
|
||||
// Float64Measure is a measure for float64 values.
|
||||
type Float64Measure struct {
|
||||
md *measureDescriptor
|
||||
}
|
||||
|
||||
// Name returns the name of the measure.
|
||||
func (m *Float64Measure) Name() string {
|
||||
return m.md.name
|
||||
}
|
||||
|
||||
// Description returns the description of the measure.
|
||||
func (m *Float64Measure) Description() string {
|
||||
return m.md.description
|
||||
}
|
||||
|
||||
// Unit returns the unit of the measure.
|
||||
func (m *Float64Measure) Unit() string {
|
||||
return m.md.unit
|
||||
*measureDescriptor
|
||||
}
|
||||
|
||||
// M creates a new float64 measurement.
|
||||
// Use Record to record measurements.
|
||||
func (m *Float64Measure) M(v float64) Measurement {
|
||||
if !m.md.subscribed() {
|
||||
return Measurement{}
|
||||
}
|
||||
return Measurement{m: m, v: v}
|
||||
return Measurement{m: m.measureDescriptor, v: v}
|
||||
}
|
||||
|
||||
// Float64 creates a new measure for float64 values.
|
||||
|
|
|
@ -17,31 +17,13 @@ package stats
|
|||
|
||||
// Int64Measure is a measure for int64 values.
|
||||
type Int64Measure struct {
|
||||
md *measureDescriptor
|
||||
}
|
||||
|
||||
// Name returns the name of the measure.
|
||||
func (m *Int64Measure) Name() string {
|
||||
return m.md.name
|
||||
}
|
||||
|
||||
// Description returns the description of the measure.
|
||||
func (m *Int64Measure) Description() string {
|
||||
return m.md.description
|
||||
}
|
||||
|
||||
// Unit returns the unit of the measure.
|
||||
func (m *Int64Measure) Unit() string {
|
||||
return m.md.unit
|
||||
*measureDescriptor
|
||||
}
|
||||
|
||||
// M creates a new int64 measurement.
|
||||
// Use Record to record measurements.
|
||||
func (m *Int64Measure) M(v int64) Measurement {
|
||||
if !m.md.subscribed() {
|
||||
return Measurement{}
|
||||
}
|
||||
return Measurement{m: m, v: float64(v)}
|
||||
return Measurement{m: m.measureDescriptor, v: float64(v)}
|
||||
}
|
||||
|
||||
// Int64 creates a new measure for int64 values.
|
||||
|
|
|
@ -18,6 +18,7 @@ package stats
|
|||
import (
|
||||
"context"
|
||||
|
||||
"go.opencensus.io/exemplar"
|
||||
"go.opencensus.io/stats/internal"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
@ -30,15 +31,19 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
// Record records one or multiple measurements with the same tags at once.
|
||||
// Record records one or multiple measurements with the same context at once.
|
||||
// If there are any tags in the context, measurements will be tagged with them.
|
||||
func Record(ctx context.Context, ms ...Measurement) {
|
||||
recorder := internal.DefaultRecorder
|
||||
if recorder == nil {
|
||||
return
|
||||
}
|
||||
if len(ms) == 0 {
|
||||
return
|
||||
}
|
||||
var record bool
|
||||
record := false
|
||||
for _, m := range ms {
|
||||
if (m != Measurement{}) {
|
||||
if m.m.subscribed() {
|
||||
record = true
|
||||
break
|
||||
}
|
||||
|
@ -46,7 +51,19 @@ func Record(ctx context.Context, ms ...Measurement) {
|
|||
if !record {
|
||||
return
|
||||
}
|
||||
if internal.DefaultRecorder != nil {
|
||||
internal.DefaultRecorder(tag.FromContext(ctx), ms)
|
||||
}
|
||||
recorder(tag.FromContext(ctx), ms, exemplar.AttachmentsFromContext(ctx))
|
||||
}
|
||||
|
||||
// RecordWithTags records one or multiple measurements at once.
|
||||
//
|
||||
// Measurements will be tagged with the tags in the context mutated by the mutators.
|
||||
// RecordWithTags is useful if you want to record with tag mutations but don't want
|
||||
// to propagate the mutations in the context.
|
||||
func RecordWithTags(ctx context.Context, mutators []tag.Mutator, ms ...Measurement) error {
|
||||
ctx, err := tag.New(ctx, mutators...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Record(ctx, ms...)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ package view
|
|||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"go.opencensus.io/exemplar"
|
||||
)
|
||||
|
||||
// AggregationData represents an aggregated value from a collection.
|
||||
|
@ -24,7 +26,7 @@ import (
|
|||
// Mosts users won't directly access aggregration data.
|
||||
type AggregationData interface {
|
||||
isAggregationData() bool
|
||||
addSample(v float64)
|
||||
addSample(e *exemplar.Exemplar)
|
||||
clone() AggregationData
|
||||
equal(other AggregationData) bool
|
||||
}
|
||||
|
@ -41,7 +43,7 @@ type CountData struct {
|
|||
|
||||
func (a *CountData) isAggregationData() bool { return true }
|
||||
|
||||
func (a *CountData) addSample(v float64) {
|
||||
func (a *CountData) addSample(_ *exemplar.Exemplar) {
|
||||
a.Value = a.Value + 1
|
||||
}
|
||||
|
||||
|
@ -68,8 +70,8 @@ type SumData struct {
|
|||
|
||||
func (a *SumData) isAggregationData() bool { return true }
|
||||
|
||||
func (a *SumData) addSample(f float64) {
|
||||
a.Value += f
|
||||
func (a *SumData) addSample(e *exemplar.Exemplar) {
|
||||
a.Value += e.Value
|
||||
}
|
||||
|
||||
func (a *SumData) clone() AggregationData {
|
||||
|
@ -88,22 +90,30 @@ func (a *SumData) equal(other AggregationData) bool {
|
|||
// Distribution aggregation.
|
||||
//
|
||||
// Most users won't directly access distribution data.
|
||||
//
|
||||
// For a distribution with N bounds, the associated DistributionData will have
|
||||
// N+1 buckets.
|
||||
type DistributionData struct {
|
||||
Count int64 // number of data points aggregated
|
||||
Min float64 // minimum value in the distribution
|
||||
Max float64 // max value in the distribution
|
||||
Mean float64 // mean of the distribution
|
||||
SumOfSquaredDev float64 // sum of the squared deviation from the mean
|
||||
CountPerBucket []int64 // number of occurrences per bucket
|
||||
bounds []float64 // histogram distribution of the values
|
||||
Count int64 // number of data points aggregated
|
||||
Min float64 // minimum value in the distribution
|
||||
Max float64 // max value in the distribution
|
||||
Mean float64 // mean of the distribution
|
||||
SumOfSquaredDev float64 // sum of the squared deviation from the mean
|
||||
CountPerBucket []int64 // number of occurrences per bucket
|
||||
// ExemplarsPerBucket is slice the same length as CountPerBucket containing
|
||||
// an exemplar for the associated bucket, or nil.
|
||||
ExemplarsPerBucket []*exemplar.Exemplar
|
||||
bounds []float64 // histogram distribution of the values
|
||||
}
|
||||
|
||||
func newDistributionData(bounds []float64) *DistributionData {
|
||||
bucketCount := len(bounds) + 1
|
||||
return &DistributionData{
|
||||
CountPerBucket: make([]int64, len(bounds)+1),
|
||||
bounds: bounds,
|
||||
Min: math.MaxFloat64,
|
||||
Max: math.SmallestNonzeroFloat64,
|
||||
CountPerBucket: make([]int64, bucketCount),
|
||||
ExemplarsPerBucket: make([]*exemplar.Exemplar, bucketCount),
|
||||
bounds: bounds,
|
||||
Min: math.MaxFloat64,
|
||||
Max: math.SmallestNonzeroFloat64,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,7 +129,8 @@ func (a *DistributionData) variance() float64 {
|
|||
|
||||
func (a *DistributionData) isAggregationData() bool { return true }
|
||||
|
||||
func (a *DistributionData) addSample(f float64) {
|
||||
func (a *DistributionData) addSample(e *exemplar.Exemplar) {
|
||||
f := e.Value
|
||||
if f < a.Min {
|
||||
a.Min = f
|
||||
}
|
||||
|
@ -127,7 +138,7 @@ func (a *DistributionData) addSample(f float64) {
|
|||
a.Max = f
|
||||
}
|
||||
a.Count++
|
||||
a.incrementBucketCount(f)
|
||||
a.addToBucket(e)
|
||||
|
||||
if a.Count == 1 {
|
||||
a.Mean = f
|
||||
|
@ -139,26 +150,43 @@ func (a *DistributionData) addSample(f float64) {
|
|||
a.SumOfSquaredDev = a.SumOfSquaredDev + (f-oldMean)*(f-a.Mean)
|
||||
}
|
||||
|
||||
func (a *DistributionData) incrementBucketCount(f float64) {
|
||||
if len(a.bounds) == 0 {
|
||||
a.CountPerBucket[0]++
|
||||
return
|
||||
}
|
||||
|
||||
func (a *DistributionData) addToBucket(e *exemplar.Exemplar) {
|
||||
var count *int64
|
||||
var ex **exemplar.Exemplar
|
||||
for i, b := range a.bounds {
|
||||
if f < b {
|
||||
a.CountPerBucket[i]++
|
||||
return
|
||||
if e.Value < b {
|
||||
count = &a.CountPerBucket[i]
|
||||
ex = &a.ExemplarsPerBucket[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
a.CountPerBucket[len(a.bounds)]++
|
||||
if count == nil {
|
||||
count = &a.CountPerBucket[len(a.bounds)]
|
||||
ex = &a.ExemplarsPerBucket[len(a.bounds)]
|
||||
}
|
||||
*count++
|
||||
*ex = maybeRetainExemplar(*ex, e)
|
||||
}
|
||||
|
||||
func maybeRetainExemplar(old, cur *exemplar.Exemplar) *exemplar.Exemplar {
|
||||
if old == nil {
|
||||
return cur
|
||||
}
|
||||
|
||||
// Heuristic to pick the "better" exemplar: first keep the one with a
|
||||
// sampled trace attachment, if neither have a trace attachment, pick the
|
||||
// one with more attachments.
|
||||
_, haveTraceID := cur.Attachments[exemplar.KeyTraceID]
|
||||
if haveTraceID || len(cur.Attachments) >= len(old.Attachments) {
|
||||
return cur
|
||||
}
|
||||
return old
|
||||
}
|
||||
|
||||
func (a *DistributionData) clone() AggregationData {
|
||||
counts := make([]int64, len(a.CountPerBucket))
|
||||
copy(counts, a.CountPerBucket)
|
||||
c := *a
|
||||
c.CountPerBucket = counts
|
||||
c.CountPerBucket = append([]int64(nil), a.CountPerBucket...)
|
||||
c.ExemplarsPerBucket = append([]*exemplar.Exemplar(nil), a.ExemplarsPerBucket...)
|
||||
return &c
|
||||
}
|
||||
|
||||
|
@ -190,8 +218,8 @@ func (l *LastValueData) isAggregationData() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (l *LastValueData) addSample(v float64) {
|
||||
l.Value = v
|
||||
func (l *LastValueData) addSample(e *exemplar.Exemplar) {
|
||||
l.Value = e.Value
|
||||
}
|
||||
|
||||
func (l *LastValueData) clone() AggregationData {
|
||||
|
|
|
@ -18,6 +18,8 @@ package view
|
|||
import (
|
||||
"sort"
|
||||
|
||||
"go.opencensus.io/exemplar"
|
||||
|
||||
"go.opencensus.io/internal/tagencoding"
|
||||
"go.opencensus.io/tag"
|
||||
)
|
||||
|
@ -31,13 +33,13 @@ type collector struct {
|
|||
a *Aggregation
|
||||
}
|
||||
|
||||
func (c *collector) addSample(s string, v float64) {
|
||||
func (c *collector) addSample(s string, e *exemplar.Exemplar) {
|
||||
aggregator, ok := c.signatures[s]
|
||||
if !ok {
|
||||
aggregator = c.a.newData()
|
||||
c.signatures[s] = aggregator
|
||||
}
|
||||
aggregator.addSample(v)
|
||||
aggregator.addSample(e)
|
||||
}
|
||||
|
||||
// collectRows returns a snapshot of the collected Row values.
|
||||
|
|
|
@ -13,33 +13,34 @@
|
|||
// limitations under the License.
|
||||
//
|
||||
|
||||
/*
|
||||
Package view contains support for collecting and exposing aggregates over stats.
|
||||
|
||||
In order to collect measurements, views need to be defined and registered.
|
||||
A view allows recorded measurements to be filtered and aggregated over a time window.
|
||||
|
||||
All recorded measurements can be filtered by a list of tags.
|
||||
|
||||
OpenCensus provides several aggregation methods: count, distribution and sum.
|
||||
Count aggregation only counts the number of measurement points. Distribution
|
||||
aggregation provides statistical summary of the aggregated data. Sum distribution
|
||||
sums up the measurement points. Aggregations are cumulative.
|
||||
|
||||
Users can dynamically create and delete views.
|
||||
|
||||
Libraries can export their own views and claim the view names
|
||||
by registering them themselves.
|
||||
|
||||
Exporting
|
||||
|
||||
Collected and aggregated data can be exported to a metric collection
|
||||
backend by registering its exporter.
|
||||
|
||||
Multiple exporters can be registered to upload the data to various
|
||||
different backends. Users need to unregister the exporters once they
|
||||
no longer are needed.
|
||||
*/
|
||||
// Package view contains support for collecting and exposing aggregates over stats.
|
||||
//
|
||||
// In order to collect measurements, views need to be defined and registered.
|
||||
// A view allows recorded measurements to be filtered and aggregated.
|
||||
//
|
||||
// All recorded measurements can be grouped by a list of tags.
|
||||
//
|
||||
// OpenCensus provides several aggregation methods: Count, Distribution and Sum.
|
||||
//
|
||||
// Count only counts the number of measurement points recorded.
|
||||
// Distribution provides statistical summary of the aggregated data by counting
|
||||
// how many recorded measurements fall into each bucket.
|
||||
// Sum adds up the measurement values.
|
||||
// LastValue just keeps track of the most recently recorded measurement value.
|
||||
// All aggregations are cumulative.
|
||||
//
|
||||
// Views can be registerd and unregistered at any time during program execution.
|
||||
//
|
||||
// Libraries can define views but it is recommended that in most cases registering
|
||||
// views be left up to applications.
|
||||
//
|
||||
// Exporting
|
||||
//
|
||||
// Collected and aggregated data can be exported to a metric collection
|
||||
// backend by registering its exporter.
|
||||
//
|
||||
// Multiple exporters can be registered to upload the data to various
|
||||
// different back ends.
|
||||
package view // import "go.opencensus.io/stats/view"
|
||||
|
||||
// TODO(acetechnologist): Add a link to the language independent OpenCensus
|
||||
|
|
|
@ -23,6 +23,8 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/exemplar"
|
||||
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/internal"
|
||||
"go.opencensus.io/tag"
|
||||
|
@ -127,12 +129,12 @@ func (v *viewInternal) collectedRows() []*Row {
|
|||
return v.collector.collectedRows(v.view.TagKeys)
|
||||
}
|
||||
|
||||
func (v *viewInternal) addSample(m *tag.Map, val float64) {
|
||||
func (v *viewInternal) addSample(m *tag.Map, e *exemplar.Exemplar) {
|
||||
if !v.isSubscribed() {
|
||||
return
|
||||
}
|
||||
sig := string(encodeWithKeys(m, v.view.TagKeys))
|
||||
v.collector.addSample(sig, val)
|
||||
v.collector.addSample(sig, e)
|
||||
}
|
||||
|
||||
// A Data is a set of rows about usage of the single measure associated
|
||||
|
|
|
@ -107,10 +107,12 @@ func RetrieveData(viewName string) ([]*Row, error) {
|
|||
return resp.rows, resp.err
|
||||
}
|
||||
|
||||
func record(tags *tag.Map, ms interface{}) {
|
||||
func record(tags *tag.Map, ms interface{}, attachments map[string]string) {
|
||||
req := &recordReq{
|
||||
tm: tags,
|
||||
ms: ms.([]stats.Measurement),
|
||||
tm: tags,
|
||||
ms: ms.([]stats.Measurement),
|
||||
attachments: attachments,
|
||||
t: time.Now(),
|
||||
}
|
||||
defaultWorker.c <- req
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/exemplar"
|
||||
|
||||
"go.opencensus.io/stats"
|
||||
"go.opencensus.io/stats/internal"
|
||||
"go.opencensus.io/tag"
|
||||
|
@ -140,8 +142,10 @@ func (cmd *retrieveDataReq) handleCommand(w *worker) {
|
|||
// recordReq is the command to record data related to multiple measures
|
||||
// at once.
|
||||
type recordReq struct {
|
||||
tm *tag.Map
|
||||
ms []stats.Measurement
|
||||
tm *tag.Map
|
||||
ms []stats.Measurement
|
||||
attachments map[string]string
|
||||
t time.Time
|
||||
}
|
||||
|
||||
func (cmd *recordReq) handleCommand(w *worker) {
|
||||
|
@ -151,7 +155,12 @@ func (cmd *recordReq) handleCommand(w *worker) {
|
|||
}
|
||||
ref := w.getMeasureRef(m.Measure().Name())
|
||||
for v := range ref.views {
|
||||
v.addSample(cmd.tm, m.Value())
|
||||
e := &exemplar.Exemplar{
|
||||
Value: m.Value(),
|
||||
Timestamp: cmd.t,
|
||||
Attachments: cmd.attachments,
|
||||
}
|
||||
v.addSample(cmd.tm, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
|
||||
package tag
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opencensus.io/exemplar"
|
||||
)
|
||||
|
||||
// FromContext returns the tag map stored in the context.
|
||||
func FromContext(ctx context.Context) *Map {
|
||||
|
@ -39,3 +43,25 @@ func NewContext(ctx context.Context, m *Map) context.Context {
|
|||
type ctxKey struct{}
|
||||
|
||||
var mapCtxKey = ctxKey{}
|
||||
|
||||
func init() {
|
||||
exemplar.RegisterAttachmentExtractor(extractTagsAttachments)
|
||||
}
|
||||
|
||||
func extractTagsAttachments(ctx context.Context, a exemplar.Attachments) exemplar.Attachments {
|
||||
m := FromContext(ctx)
|
||||
if m == nil {
|
||||
return a
|
||||
}
|
||||
if len(m.m) == 0 {
|
||||
return a
|
||||
}
|
||||
if a == nil {
|
||||
a = make(map[string]string)
|
||||
}
|
||||
|
||||
for k, v := range m.m {
|
||||
a[exemplar.KeyPrefixTag+k.Name()] = v
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2018, OpenCensus Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
|
||||
"go.opencensus.io/exemplar"
|
||||
)
|
||||
|
||||
func init() {
|
||||
exemplar.RegisterAttachmentExtractor(attachSpanContext)
|
||||
}
|
||||
|
||||
func attachSpanContext(ctx context.Context, a exemplar.Attachments) exemplar.Attachments {
|
||||
span := FromContext(ctx)
|
||||
if span == nil {
|
||||
return a
|
||||
}
|
||||
sc := span.SpanContext()
|
||||
if !sc.IsSampled() {
|
||||
return a
|
||||
}
|
||||
if a == nil {
|
||||
a = make(exemplar.Attachments)
|
||||
}
|
||||
a[exemplar.KeyTraceID] = hex.EncodeToString(sc.TraceID[:])
|
||||
a[exemplar.KeySpanID] = hex.EncodeToString(sc.SpanID[:])
|
||||
return a
|
||||
}
|
|
@ -243,13 +243,16 @@ func startSpanInternal(name string, hasParent bool, parent SpanContext, remotePa
|
|||
|
||||
// End ends the span.
|
||||
func (s *Span) End() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
if s.executionTracerTaskEnd != nil {
|
||||
s.executionTracerTaskEnd()
|
||||
}
|
||||
if !s.IsRecordingEvents() {
|
||||
return
|
||||
}
|
||||
s.endOnce.Do(func() {
|
||||
if s.executionTracerTaskEnd != nil {
|
||||
s.executionTracerTaskEnd()
|
||||
}
|
||||
exp, _ := exporters.Load().(exportersMap)
|
||||
mustExport := s.spanContext.IsSampled() && len(exp) > 0
|
||||
if s.spanStore != nil || mustExport {
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package tracestate implements support for the Tracestate header of the
|
||||
// W3C TraceContext propagation format.
|
||||
package tracestate
|
||||
|
||||
import (
|
||||
|
|
|
@ -19,54 +19,12 @@ See godoc for further documentation and examples.
|
|||
* [godoc.org/golang.org/x/oauth2](http://godoc.org/golang.org/x/oauth2)
|
||||
* [godoc.org/golang.org/x/oauth2/google](http://godoc.org/golang.org/x/oauth2/google)
|
||||
|
||||
## Policy for new packages
|
||||
|
||||
## App Engine
|
||||
|
||||
In change 96e89be (March 2015), we removed the `oauth2.Context2` type in favor
|
||||
of the [`context.Context`](https://golang.org/x/net/context#Context) type from
|
||||
the `golang.org/x/net/context` package
|
||||
|
||||
This means it's no longer possible to use the "Classic App Engine"
|
||||
`appengine.Context` type with the `oauth2` package. (You're using
|
||||
Classic App Engine if you import the package `"appengine"`.)
|
||||
|
||||
To work around this, you may use the new `"google.golang.org/appengine"`
|
||||
package. This package has almost the same API as the `"appengine"` package,
|
||||
but it can be fetched with `go get` and used on "Managed VMs" and well as
|
||||
Classic App Engine.
|
||||
|
||||
See the [new `appengine` package's readme](https://github.com/golang/appengine#updating-a-go-app-engine-app)
|
||||
for information on updating your app.
|
||||
|
||||
If you don't want to update your entire app to use the new App Engine packages,
|
||||
you may use both sets of packages in parallel, using only the new packages
|
||||
with the `oauth2` package.
|
||||
|
||||
```go
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
newappengine "google.golang.org/appengine"
|
||||
newurlfetch "google.golang.org/appengine/urlfetch"
|
||||
|
||||
"appengine"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
var c appengine.Context = appengine.NewContext(r)
|
||||
c.Infof("Logging a message with the old package")
|
||||
|
||||
var ctx context.Context = newappengine.NewContext(r)
|
||||
client := &http.Client{
|
||||
Transport: &oauth2.Transport{
|
||||
Source: google.AppEngineTokenSource(ctx, "scope"),
|
||||
Base: &newurlfetch.Transport{Context: ctx},
|
||||
},
|
||||
}
|
||||
client.Get("...")
|
||||
}
|
||||
```
|
||||
We no longer accept new provider-specific packages in this repo. For
|
||||
defining provider endpoints and provider-specific OAuth2 behavior, we
|
||||
encourage you to create packages elsewhere. We'll keep the existing
|
||||
packages for compatibility.
|
||||
|
||||
## Report Issues / Send Patches
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
module golang.org/x/oauth2
|
||||
|
||||
go 1.11
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.34.0
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
|
||||
google.golang.org/appengine v1.4.0
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
@ -5,85 +5,34 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// appengineFlex is set at init time by appengineflex_hook.go. If true, we are on App Engine Flex.
|
||||
var appengineFlex bool
|
||||
|
||||
// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
|
||||
// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible.
|
||||
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error)
|
||||
|
||||
// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
|
||||
// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible.
|
||||
var appengineAppIDFunc func(c context.Context) string
|
||||
|
||||
// AppEngineTokenSource returns a token source that fetches tokens
|
||||
// issued to the current App Engine application's service account.
|
||||
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
|
||||
// that involves user accounts, see oauth2.Config instead.
|
||||
// AppEngineTokenSource returns a token source that fetches tokens from either
|
||||
// the current application's service account or from the metadata server,
|
||||
// depending on the App Engine environment. See below for environment-specific
|
||||
// details. If you are implementing a 3-legged OAuth 2.0 flow on App Engine that
|
||||
// involves user accounts, see oauth2.Config instead.
|
||||
//
|
||||
// The provided context must have come from appengine.NewContext.
|
||||
// First generation App Engine runtimes (<= Go 1.9):
|
||||
// AppEngineTokenSource returns a token source that fetches tokens issued to the
|
||||
// current App Engine application's service account. The provided context must have
|
||||
// come from appengine.NewContext.
|
||||
//
|
||||
// Second generation App Engine runtimes (>= Go 1.11) and App Engine flexible:
|
||||
// AppEngineTokenSource is DEPRECATED on second generation runtimes and on the
|
||||
// flexible environment. It delegates to ComputeTokenSource, and the provided
|
||||
// context and scopes are not used. Please use DefaultTokenSource (or ComputeTokenSource,
|
||||
// which DefaultTokenSource will use in this case) instead.
|
||||
func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource {
|
||||
if appengineTokenFunc == nil {
|
||||
panic("google: AppEngineTokenSource can only be used on App Engine.")
|
||||
}
|
||||
scopes := append([]string{}, scope...)
|
||||
sort.Strings(scopes)
|
||||
return &appEngineTokenSource{
|
||||
ctx: ctx,
|
||||
scopes: scopes,
|
||||
key: strings.Join(scopes, " "),
|
||||
}
|
||||
}
|
||||
|
||||
// aeTokens helps the fetched tokens to be reused until their expiration.
|
||||
var (
|
||||
aeTokensMu sync.Mutex
|
||||
aeTokens = make(map[string]*tokenLock) // key is space-separated scopes
|
||||
)
|
||||
|
||||
type tokenLock struct {
|
||||
mu sync.Mutex // guards t; held while fetching or updating t
|
||||
t *oauth2.Token
|
||||
}
|
||||
|
||||
type appEngineTokenSource struct {
|
||||
ctx context.Context
|
||||
scopes []string
|
||||
key string // to aeTokens map; space-separated scopes
|
||||
}
|
||||
|
||||
func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) {
|
||||
if appengineTokenFunc == nil {
|
||||
panic("google: AppEngineTokenSource can only be used on App Engine.")
|
||||
}
|
||||
|
||||
aeTokensMu.Lock()
|
||||
tok, ok := aeTokens[ts.key]
|
||||
if !ok {
|
||||
tok = &tokenLock{}
|
||||
aeTokens[ts.key] = tok
|
||||
}
|
||||
aeTokensMu.Unlock()
|
||||
|
||||
tok.mu.Lock()
|
||||
defer tok.mu.Unlock()
|
||||
if tok.t.Valid() {
|
||||
return tok.t, nil
|
||||
}
|
||||
access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok.t = &oauth2.Token{
|
||||
AccessToken: access,
|
||||
Expiry: exp,
|
||||
}
|
||||
return tok.t, nil
|
||||
return appEngineTokenSource(ctx, scope...)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
// This file applies to App Engine first generation runtimes (<= Go 1.9).
|
||||
|
||||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/appengine"
|
||||
)
|
||||
|
||||
func init() {
|
||||
appengineTokenFunc = appengine.AccessToken
|
||||
appengineAppIDFunc = appengine.AppID
|
||||
}
|
||||
|
||||
// See comment on AppEngineTokenSource in appengine.go.
|
||||
func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource {
|
||||
scopes := append([]string{}, scope...)
|
||||
sort.Strings(scopes)
|
||||
return &gaeTokenSource{
|
||||
ctx: ctx,
|
||||
scopes: scopes,
|
||||
key: strings.Join(scopes, " "),
|
||||
}
|
||||
}
|
||||
|
||||
// aeTokens helps the fetched tokens to be reused until their expiration.
|
||||
var (
|
||||
aeTokensMu sync.Mutex
|
||||
aeTokens = make(map[string]*tokenLock) // key is space-separated scopes
|
||||
)
|
||||
|
||||
type tokenLock struct {
|
||||
mu sync.Mutex // guards t; held while fetching or updating t
|
||||
t *oauth2.Token
|
||||
}
|
||||
|
||||
type gaeTokenSource struct {
|
||||
ctx context.Context
|
||||
scopes []string
|
||||
key string // to aeTokens map; space-separated scopes
|
||||
}
|
||||
|
||||
func (ts *gaeTokenSource) Token() (*oauth2.Token, error) {
|
||||
aeTokensMu.Lock()
|
||||
tok, ok := aeTokens[ts.key]
|
||||
if !ok {
|
||||
tok = &tokenLock{}
|
||||
aeTokens[ts.key] = tok
|
||||
}
|
||||
aeTokensMu.Unlock()
|
||||
|
||||
tok.mu.Lock()
|
||||
defer tok.mu.Unlock()
|
||||
if tok.t.Valid() {
|
||||
return tok.t, nil
|
||||
}
|
||||
access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tok.t = &oauth2.Token{
|
||||
AccessToken: access,
|
||||
Expiry: exp,
|
||||
}
|
||||
return tok.t, nil
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
// This file applies to App Engine second generation runtimes (>= Go 1.11) and App Engine flexible.
|
||||
|
||||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var logOnce sync.Once // only spam about deprecation once
|
||||
|
||||
// See comment on AppEngineTokenSource in appengine.go.
|
||||
func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource {
|
||||
logOnce.Do(func() {
|
||||
log.Print("google: AppEngineTokenSource is deprecated on App Engine standard second generation runtimes (>= Go 1.11) and App Engine flexible. Please use DefaultTokenSource or ComputeTokenSource.")
|
||||
})
|
||||
return ComputeTokenSource("")
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine appenginevm
|
||||
|
||||
package google
|
||||
|
||||
import "google.golang.org/appengine"
|
||||
|
||||
func init() {
|
||||
appengineTokenFunc = appengine.AccessToken
|
||||
appengineAppIDFunc = appengine.AppID
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appenginevm
|
||||
|
||||
package google
|
||||
|
||||
func init() {
|
||||
appengineFlex = true // Flex doesn't support appengine.AccessToken; depend on metadata server.
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -14,10 +15,28 @@ import (
|
|||
"runtime"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Credentials holds Google credentials, including "Application Default Credentials".
|
||||
// For more details, see:
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials
|
||||
type Credentials struct {
|
||||
ProjectID string // may be empty
|
||||
TokenSource oauth2.TokenSource
|
||||
|
||||
// JSON contains the raw bytes from a JSON credentials file.
|
||||
// This field may be nil if authentication is provided by the
|
||||
// environment and not with a credentials file, e.g. when code is
|
||||
// running on Google Cloud Platform.
|
||||
JSON []byte
|
||||
}
|
||||
|
||||
// DefaultCredentials is the old name of Credentials.
|
||||
//
|
||||
// Deprecated: use Credentials instead.
|
||||
type DefaultCredentials = Credentials
|
||||
|
||||
// DefaultClient returns an HTTP Client that uses the
|
||||
// DefaultTokenSource to obtain authentication credentials.
|
||||
func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
|
||||
|
@ -39,8 +58,23 @@ func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSourc
|
|||
return creds.TokenSource, nil
|
||||
}
|
||||
|
||||
// Common implementation for FindDefaultCredentials.
|
||||
func findDefaultCredentials(ctx context.Context, scopes []string) (*DefaultCredentials, error) {
|
||||
// FindDefaultCredentials searches for "Application Default Credentials".
|
||||
//
|
||||
// It looks for credentials in the following places,
|
||||
// preferring the first location found:
|
||||
//
|
||||
// 1. A JSON file whose path is specified by the
|
||||
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
|
||||
// 2. A JSON file in a location known to the gcloud command-line tool.
|
||||
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
|
||||
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
|
||||
// 3. On Google App Engine standard first generation runtimes (<= Go 1.9) it uses
|
||||
// the appengine.AccessToken function.
|
||||
// 4. On Google Compute Engine, Google App Engine standard second generation runtimes
|
||||
// (>= Go 1.11), and Google App Engine flexible environment, it fetches
|
||||
// credentials from the metadata server.
|
||||
// (In this final case any provided scopes are ignored.)
|
||||
func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) {
|
||||
// First, try the environment variable.
|
||||
const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||
if filename := os.Getenv(envVar); filename != "" {
|
||||
|
@ -59,15 +93,18 @@ func findDefaultCredentials(ctx context.Context, scopes []string) (*DefaultCrede
|
|||
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err)
|
||||
}
|
||||
|
||||
// Third, if we're on Google App Engine use those credentials.
|
||||
if appengineTokenFunc != nil && !appengineFlex {
|
||||
// Third, if we're on a Google App Engine standard first generation runtime (<= Go 1.9)
|
||||
// use those credentials. App Engine standard second generation runtimes (>= Go 1.11)
|
||||
// and App Engine flexible use ComputeTokenSource and the metadata server.
|
||||
if appengineTokenFunc != nil {
|
||||
return &DefaultCredentials{
|
||||
ProjectID: appengineAppIDFunc(ctx),
|
||||
TokenSource: AppEngineTokenSource(ctx, scopes...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Fourth, if we're on Google Compute Engine use the metadata server.
|
||||
// Fourth, if we're on Google Compute Engine, an App Engine standard second generation runtime,
|
||||
// or App Engine flexible, use the metadata server.
|
||||
if metadata.OnGCE() {
|
||||
id, _ := metadata.ProjectID()
|
||||
return &DefaultCredentials{
|
||||
|
@ -81,8 +118,11 @@ func findDefaultCredentials(ctx context.Context, scopes []string) (*DefaultCrede
|
|||
return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url)
|
||||
}
|
||||
|
||||
// Common implementation for CredentialsFromJSON.
|
||||
func credentialsFromJSON(ctx context.Context, jsonData []byte, scopes []string) (*DefaultCredentials, error) {
|
||||
// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can
|
||||
// represent either a Google Developers Console client_credentials.json file (as in
|
||||
// ConfigFromJSON) or a Google Developers service account key file (as in
|
||||
// JWTConfigFromJSON).
|
||||
func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) {
|
||||
var f credentialsFile
|
||||
if err := json.Unmarshal(jsonData, &f); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.9
|
||||
|
||||
// Package google provides support for making OAuth2 authorized and authenticated
|
||||
// HTTP requests to Google APIs. It supports the Web server flow, client-side
|
||||
// credentials, service accounts, Google Compute Engine service accounts, and Google
|
|
@ -1,43 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.9
|
||||
|
||||
// Package google provides support for making OAuth2 authorized and authenticated
|
||||
// HTTP requests to Google APIs. It supports the Web server flow, client-side
|
||||
// credentials, service accounts, Google Compute Engine service accounts, and Google
|
||||
// App Engine service accounts.
|
||||
//
|
||||
// A brief overview of the package follows. For more information, please read
|
||||
// https://developers.google.com/accounts/docs/OAuth2
|
||||
// and
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials.
|
||||
//
|
||||
// OAuth2 Configs
|
||||
//
|
||||
// Two functions in this package return golang.org/x/oauth2.Config values from Google credential
|
||||
// data. Google supports two JSON formats for OAuth2 credentials: one is handled by ConfigFromJSON,
|
||||
// the other by JWTConfigFromJSON. The returned Config can be used to obtain a TokenSource or
|
||||
// create an http.Client.
|
||||
//
|
||||
//
|
||||
// Credentials
|
||||
//
|
||||
// The DefaultCredentials type represents Google Application Default Credentials, as
|
||||
// well as other forms of credential.
|
||||
//
|
||||
// Use FindDefaultCredentials to obtain Application Default Credentials.
|
||||
// FindDefaultCredentials looks in some well-known places for a credentials file, and
|
||||
// will call AppEngineTokenSource or ComputeTokenSource as needed.
|
||||
//
|
||||
// DefaultClient and DefaultTokenSource are convenience methods. They first call FindDefaultCredentials,
|
||||
// then use the credentials to construct an http.Client or an oauth2.TokenSource.
|
||||
//
|
||||
// Use CredentialsFromJSON to obtain credentials from either of the two JSON
|
||||
// formats described in OAuth2 Configs, above. (The DefaultCredentials returned may
|
||||
// not be "Application Default Credentials".) The TokenSource in the returned value
|
||||
// is the same as the one obtained from the oauth2.Config returned from
|
||||
// ConfigFromJSON or JWTConfigFromJSON, but the DefaultCredentials may contain
|
||||
// additional information that is useful is some circumstances.
|
||||
package google // import "golang.org/x/oauth2/google"
|
|
@ -1,57 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.9
|
||||
|
||||
package google
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Credentials holds Google credentials, including "Application Default Credentials".
|
||||
// For more details, see:
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials
|
||||
type Credentials struct {
|
||||
ProjectID string // may be empty
|
||||
TokenSource oauth2.TokenSource
|
||||
|
||||
// JSON contains the raw bytes from a JSON credentials file.
|
||||
// This field may be nil if authentication is provided by the
|
||||
// environment and not with a credentials file, e.g. when code is
|
||||
// running on Google Cloud Platform.
|
||||
JSON []byte
|
||||
}
|
||||
|
||||
// DefaultCredentials is the old name of Credentials.
|
||||
//
|
||||
// Deprecated: use Credentials instead.
|
||||
type DefaultCredentials = Credentials
|
||||
|
||||
// FindDefaultCredentials searches for "Application Default Credentials".
|
||||
//
|
||||
// It looks for credentials in the following places,
|
||||
// preferring the first location found:
|
||||
//
|
||||
// 1. A JSON file whose path is specified by the
|
||||
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
|
||||
// 2. A JSON file in a location known to the gcloud command-line tool.
|
||||
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
|
||||
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
|
||||
// 3. On Google App Engine it uses the appengine.AccessToken function.
|
||||
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
|
||||
// credentials from the metadata server.
|
||||
// (In this final case any provided scopes are ignored.)
|
||||
func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) {
|
||||
return findDefaultCredentials(ctx, scopes)
|
||||
}
|
||||
|
||||
// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can
|
||||
// represent either a Google Developers Console client_credentials.json file (as in
|
||||
// ConfigFromJSON) or a Google Developers service account key file (as in
|
||||
// JWTConfigFromJSON).
|
||||
func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) {
|
||||
return credentialsFromJSON(ctx, jsonData, scopes)
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -12,15 +13,15 @@ import (
|
|||
"time"
|
||||
|
||||
"cloud.google.com/go/compute/metadata"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
// Endpoint is Google's OAuth 2.0 endpoint.
|
||||
var Endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||
AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||
AuthStyle: oauth2.AuthStyleInParams,
|
||||
}
|
||||
|
||||
// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.9
|
||||
|
||||
package google
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// DefaultCredentials holds Google credentials, including "Application Default Credentials".
|
||||
// For more details, see:
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials
|
||||
type DefaultCredentials struct {
|
||||
ProjectID string // may be empty
|
||||
TokenSource oauth2.TokenSource
|
||||
|
||||
// JSON contains the raw bytes from a JSON credentials file.
|
||||
// This field may be nil if authentication is provided by the
|
||||
// environment and not with a credentials file, e.g. when code is
|
||||
// running on Google Cloud Platform.
|
||||
JSON []byte
|
||||
}
|
||||
|
||||
// FindDefaultCredentials searches for "Application Default Credentials".
|
||||
//
|
||||
// It looks for credentials in the following places,
|
||||
// preferring the first location found:
|
||||
//
|
||||
// 1. A JSON file whose path is specified by the
|
||||
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
|
||||
// 2. A JSON file in a location known to the gcloud command-line tool.
|
||||
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
|
||||
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
|
||||
// 3. On Google App Engine it uses the appengine.AccessToken function.
|
||||
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
|
||||
// credentials from the metadata server.
|
||||
// (In this final case any provided scopes are ignored.)
|
||||
func FindDefaultCredentials(ctx context.Context, scopes ...string) (*DefaultCredentials, error) {
|
||||
return findDefaultCredentials(ctx, scopes)
|
||||
}
|
||||
|
||||
// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can
|
||||
// represent either a Google Developers Console client_credentials.json file (as in
|
||||
// ConfigFromJSON) or a Google Developers service account key file (as in
|
||||
// JWTConfigFromJSON).
|
||||
//
|
||||
// Note: despite the name, the returned credentials may not be Application Default Credentials.
|
||||
func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*DefaultCredentials, error) {
|
||||
return credentialsFromJSON(ctx, jsonData, scopes)
|
||||
}
|
|
@ -6,6 +6,7 @@ package google
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -18,7 +19,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ func ParseKey(key []byte) (*rsa.PrivateKey, error) {
|
|||
if err != nil {
|
||||
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v", err)
|
||||
return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8; parse error: %v", err)
|
||||
}
|
||||
}
|
||||
parsed, ok := parsedKey.(*rsa.PrivateKey)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -15,9 +16,9 @@ import (
|
|||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
)
|
||||
|
||||
|
@ -77,6 +78,9 @@ func (e *tokenJSON) expiry() (t time.Time) {
|
|||
type expirationTime int32
|
||||
|
||||
func (e *expirationTime) UnmarshalJSON(b []byte) error {
|
||||
if len(b) == 0 || string(b) == "null" {
|
||||
return nil
|
||||
}
|
||||
var n json.Number
|
||||
err := json.Unmarshal(b, &n)
|
||||
if err != nil {
|
||||
|
@ -90,98 +94,71 @@ func (e *expirationTime) UnmarshalJSON(b []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var brokenAuthHeaderProviders = []string{
|
||||
"https://accounts.google.com/",
|
||||
"https://api.codeswholesale.com/oauth/token",
|
||||
"https://api.dropbox.com/",
|
||||
"https://api.dropboxapi.com/",
|
||||
"https://api.instagram.com/",
|
||||
"https://api.netatmo.net/",
|
||||
"https://api.odnoklassniki.ru/",
|
||||
"https://api.pushbullet.com/",
|
||||
"https://api.soundcloud.com/",
|
||||
"https://api.twitch.tv/",
|
||||
"https://id.twitch.tv/",
|
||||
"https://app.box.com/",
|
||||
"https://api.box.com/",
|
||||
"https://connect.stripe.com/",
|
||||
"https://login.mailchimp.com/",
|
||||
"https://login.microsoftonline.com/",
|
||||
"https://login.salesforce.com/",
|
||||
"https://login.windows.net",
|
||||
"https://login.live.com/",
|
||||
"https://login.live-int.com/",
|
||||
"https://oauth.sandbox.trainingpeaks.com/",
|
||||
"https://oauth.trainingpeaks.com/",
|
||||
"https://oauth.vk.com/",
|
||||
"https://openapi.baidu.com/",
|
||||
"https://slack.com/",
|
||||
"https://test-sandbox.auth.corp.google.com",
|
||||
"https://test.salesforce.com/",
|
||||
"https://user.gini.net/",
|
||||
"https://www.douban.com/",
|
||||
"https://www.googleapis.com/",
|
||||
"https://www.linkedin.com/",
|
||||
"https://www.strava.com/oauth/",
|
||||
"https://www.wunderlist.com/oauth/",
|
||||
"https://api.patreon.com/",
|
||||
"https://sandbox.codeswholesale.com/oauth/token",
|
||||
"https://api.sipgate.com/v1/authorization/oauth",
|
||||
"https://api.medium.com/v1/tokens",
|
||||
"https://log.finalsurge.com/oauth/token",
|
||||
"https://multisport.todaysplan.com.au/rest/oauth/access_token",
|
||||
"https://whats.todaysplan.com.au/rest/oauth/access_token",
|
||||
"https://stackoverflow.com/oauth/access_token",
|
||||
"https://account.health.nokia.com",
|
||||
// RegisterBrokenAuthHeaderProvider previously did something. It is now a no-op.
|
||||
//
|
||||
// Deprecated: this function no longer does anything. Caller code that
|
||||
// wants to avoid potential extra HTTP requests made during
|
||||
// auto-probing of the provider's auth style should set
|
||||
// Endpoint.AuthStyle.
|
||||
func RegisterBrokenAuthHeaderProvider(tokenURL string) {}
|
||||
|
||||
// AuthStyle is a copy of the golang.org/x/oauth2 package's AuthStyle type.
|
||||
type AuthStyle int
|
||||
|
||||
const (
|
||||
AuthStyleUnknown AuthStyle = 0
|
||||
AuthStyleInParams AuthStyle = 1
|
||||
AuthStyleInHeader AuthStyle = 2
|
||||
)
|
||||
|
||||
// authStyleCache is the set of tokenURLs we've successfully used via
|
||||
// RetrieveToken and which style auth we ended up using.
|
||||
// It's called a cache, but it doesn't (yet?) shrink. It's expected that
|
||||
// the set of OAuth2 servers a program contacts over time is fixed and
|
||||
// small.
|
||||
var authStyleCache struct {
|
||||
sync.Mutex
|
||||
m map[string]AuthStyle // keyed by tokenURL
|
||||
}
|
||||
|
||||
// brokenAuthHeaderDomains lists broken providers that issue dynamic endpoints.
|
||||
var brokenAuthHeaderDomains = []string{
|
||||
".auth0.com",
|
||||
".force.com",
|
||||
".myshopify.com",
|
||||
".okta.com",
|
||||
".oktapreview.com",
|
||||
// ResetAuthCache resets the global authentication style cache used
|
||||
// for AuthStyleUnknown token requests.
|
||||
func ResetAuthCache() {
|
||||
authStyleCache.Lock()
|
||||
defer authStyleCache.Unlock()
|
||||
authStyleCache.m = nil
|
||||
}
|
||||
|
||||
func RegisterBrokenAuthHeaderProvider(tokenURL string) {
|
||||
brokenAuthHeaderProviders = append(brokenAuthHeaderProviders, tokenURL)
|
||||
// lookupAuthStyle reports which auth style we last used with tokenURL
|
||||
// when calling RetrieveToken and whether we have ever done so.
|
||||
func lookupAuthStyle(tokenURL string) (style AuthStyle, ok bool) {
|
||||
authStyleCache.Lock()
|
||||
defer authStyleCache.Unlock()
|
||||
style, ok = authStyleCache.m[tokenURL]
|
||||
return
|
||||
}
|
||||
|
||||
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
|
||||
// implements the OAuth2 spec correctly
|
||||
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
||||
// In summary:
|
||||
// - Reddit only accepts client secret in the Authorization header
|
||||
// - Dropbox accepts either it in URL param or Auth header, but not both.
|
||||
// - Google only accepts URL param (not spec compliant?), not Auth header
|
||||
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
|
||||
func providerAuthHeaderWorks(tokenURL string) bool {
|
||||
for _, s := range brokenAuthHeaderProviders {
|
||||
if strings.HasPrefix(tokenURL, s) {
|
||||
// Some sites fail to implement the OAuth2 spec fully.
|
||||
return false
|
||||
}
|
||||
// setAuthStyle adds an entry to authStyleCache, documented above.
|
||||
func setAuthStyle(tokenURL string, v AuthStyle) {
|
||||
authStyleCache.Lock()
|
||||
defer authStyleCache.Unlock()
|
||||
if authStyleCache.m == nil {
|
||||
authStyleCache.m = make(map[string]AuthStyle)
|
||||
}
|
||||
|
||||
if u, err := url.Parse(tokenURL); err == nil {
|
||||
for _, s := range brokenAuthHeaderDomains {
|
||||
if strings.HasSuffix(u.Host, s) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assume the provider implements the spec properly
|
||||
// otherwise. We can add more exceptions as they're
|
||||
// discovered. We will _not_ be adding configurable hooks
|
||||
// to this package to let users select server bugs.
|
||||
return true
|
||||
authStyleCache.m[tokenURL] = v
|
||||
}
|
||||
|
||||
func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) {
|
||||
bustedAuth := !providerAuthHeaderWorks(tokenURL)
|
||||
if bustedAuth {
|
||||
// newTokenRequest returns a new *http.Request to retrieve a new token
|
||||
// from tokenURL using the provided clientID, clientSecret, and POST
|
||||
// body parameters.
|
||||
//
|
||||
// inParams is whether the clientID & clientSecret should be encoded
|
||||
// as the POST body. An 'inParams' value of true means to send it in
|
||||
// the POST body (along with any values in v); false means to send it
|
||||
// in the Authorization header.
|
||||
func newTokenRequest(tokenURL, clientID, clientSecret string, v url.Values, authStyle AuthStyle) (*http.Request, error) {
|
||||
if authStyle == AuthStyleInParams {
|
||||
v = cloneURLValues(v)
|
||||
if clientID != "" {
|
||||
v.Set("client_id", clientID)
|
||||
}
|
||||
|
@ -194,15 +171,70 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
|
|||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if !bustedAuth {
|
||||
if authStyle == AuthStyleInHeader {
|
||||
req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func cloneURLValues(v url.Values) url.Values {
|
||||
v2 := make(url.Values, len(v))
|
||||
for k, vv := range v {
|
||||
v2[k] = append([]string(nil), vv...)
|
||||
}
|
||||
return v2
|
||||
}
|
||||
|
||||
func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values, authStyle AuthStyle) (*Token, error) {
|
||||
needsAuthStyleProbe := authStyle == 0
|
||||
if needsAuthStyleProbe {
|
||||
if style, ok := lookupAuthStyle(tokenURL); ok {
|
||||
authStyle = style
|
||||
needsAuthStyleProbe = false
|
||||
} else {
|
||||
authStyle = AuthStyleInHeader // the first way we'll try
|
||||
}
|
||||
}
|
||||
req, err := newTokenRequest(tokenURL, clientID, clientSecret, v, authStyle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, err := doTokenRoundTrip(ctx, req)
|
||||
if err != nil && needsAuthStyleProbe {
|
||||
// If we get an error, assume the server wants the
|
||||
// clientID & clientSecret in a different form.
|
||||
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
||||
// In summary:
|
||||
// - Reddit only accepts client secret in the Authorization header
|
||||
// - Dropbox accepts either it in URL param or Auth header, but not both.
|
||||
// - Google only accepts URL param (not spec compliant?), not Auth header
|
||||
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
|
||||
//
|
||||
// We used to maintain a big table in this code of all the sites and which way
|
||||
// they went, but maintaining it didn't scale & got annoying.
|
||||
// So just try both ways.
|
||||
authStyle = AuthStyleInParams // the second way we'll try
|
||||
req, _ = newTokenRequest(tokenURL, clientID, clientSecret, v, authStyle)
|
||||
token, err = doTokenRoundTrip(ctx, req)
|
||||
}
|
||||
if needsAuthStyleProbe && err == nil {
|
||||
setAuthStyle(tokenURL, authStyle)
|
||||
}
|
||||
// Don't overwrite `RefreshToken` with an empty value
|
||||
// if this was a token refreshing request.
|
||||
if token != nil && token.RefreshToken == "" {
|
||||
token.RefreshToken = v.Get("refresh_token")
|
||||
}
|
||||
return token, err
|
||||
}
|
||||
|
||||
func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
|
||||
r, err := ctxhttp.Do(ctx, ContextClient(ctx), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
|
||||
r.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||
}
|
||||
|
@ -228,7 +260,7 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
|
|||
Raw: vals,
|
||||
}
|
||||
e := vals.Get("expires_in")
|
||||
if e == "" {
|
||||
if e == "" || e == "null" {
|
||||
// TODO(jbd): Facebook's OAuth2 implementation is broken and
|
||||
// returns expires_in field in expires. Remove the fallback to expires,
|
||||
// when Facebook fixes their implementation.
|
||||
|
@ -252,13 +284,8 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
|
|||
}
|
||||
json.Unmarshal(body, &token.Raw) // no error checks for optional fields
|
||||
}
|
||||
// Don't overwrite `RefreshToken` with an empty value
|
||||
// if this was a token refreshing request.
|
||||
if token.RefreshToken == "" {
|
||||
token.RefreshToken = v.Get("refresh_token")
|
||||
}
|
||||
if token.AccessToken == "" {
|
||||
return token, errors.New("oauth2: server response missing access_token")
|
||||
return nil, errors.New("oauth2: server response missing access_token")
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// HTTPClient is the context key to use with golang.org/x/net/context's
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -18,7 +19,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/internal"
|
||||
"golang.org/x/oauth2/jws"
|
||||
|
@ -61,6 +61,11 @@ type Config struct {
|
|||
|
||||
// Expires optionally specifies how long the token is valid for.
|
||||
Expires time.Duration
|
||||
|
||||
// Audience optionally specifies the intended audience of the
|
||||
// request. If empty, the value of TokenURL is used as the
|
||||
// intended audience.
|
||||
Audience string
|
||||
}
|
||||
|
||||
// TokenSource returns a JWT TokenSource using the configuration
|
||||
|
@ -105,6 +110,9 @@ func (js jwtSource) Token() (*oauth2.Token, error) {
|
|||
if t := js.conf.Expires; t > 0 {
|
||||
claimSet.Exp = time.Now().Add(t).Unix()
|
||||
}
|
||||
if aud := js.conf.Audience; aud != "" {
|
||||
claimSet.Aud = aud
|
||||
}
|
||||
h := *defaultHeader
|
||||
h.KeyID = js.conf.PrivateKeyID
|
||||
payload, err := jws.Encode(&h, claimSet, pk)
|
||||
|
|
|
@ -10,13 +10,13 @@ package oauth2 // import "golang.org/x/oauth2"
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/internal"
|
||||
)
|
||||
|
||||
|
@ -26,17 +26,13 @@ import (
|
|||
// Deprecated: Use context.Background() or context.TODO() instead.
|
||||
var NoContext = context.TODO()
|
||||
|
||||
// RegisterBrokenAuthHeaderProvider registers an OAuth2 server
|
||||
// identified by the tokenURL prefix as an OAuth2 implementation
|
||||
// which doesn't support the HTTP Basic authentication
|
||||
// scheme to authenticate with the authorization server.
|
||||
// Once a server is registered, credentials (client_id and client_secret)
|
||||
// will be passed as query parameters rather than being present
|
||||
// in the Authorization header.
|
||||
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
||||
func RegisterBrokenAuthHeaderProvider(tokenURL string) {
|
||||
internal.RegisterBrokenAuthHeaderProvider(tokenURL)
|
||||
}
|
||||
// RegisterBrokenAuthHeaderProvider previously did something. It is now a no-op.
|
||||
//
|
||||
// Deprecated: this function no longer does anything. Caller code that
|
||||
// wants to avoid potential extra HTTP requests made during
|
||||
// auto-probing of the provider's auth style should set
|
||||
// Endpoint.AuthStyle.
|
||||
func RegisterBrokenAuthHeaderProvider(tokenURL string) {}
|
||||
|
||||
// Config describes a typical 3-legged OAuth2 flow, with both the
|
||||
// client application information and the server's endpoint URLs.
|
||||
|
@ -71,13 +67,38 @@ type TokenSource interface {
|
|||
Token() (*Token, error)
|
||||
}
|
||||
|
||||
// Endpoint contains the OAuth 2.0 provider's authorization and token
|
||||
// Endpoint represents an OAuth 2.0 provider's authorization and token
|
||||
// endpoint URLs.
|
||||
type Endpoint struct {
|
||||
AuthURL string
|
||||
TokenURL string
|
||||
|
||||
// AuthStyle optionally specifies how the endpoint wants the
|
||||
// client ID & client secret sent. The zero value means to
|
||||
// auto-detect.
|
||||
AuthStyle AuthStyle
|
||||
}
|
||||
|
||||
// AuthStyle represents how requests for tokens are authenticated
|
||||
// to the server.
|
||||
type AuthStyle int
|
||||
|
||||
const (
|
||||
// AuthStyleAutoDetect means to auto-detect which authentication
|
||||
// style the provider wants by trying both ways and caching
|
||||
// the successful way for the future.
|
||||
AuthStyleAutoDetect AuthStyle = 0
|
||||
|
||||
// AuthStyleInParams sends the "client_id" and "client_secret"
|
||||
// in the POST body as application/x-www-form-urlencoded parameters.
|
||||
AuthStyleInParams AuthStyle = 1
|
||||
|
||||
// AuthStyleInHeader sends the client_id and client_password
|
||||
// using HTTP Basic Authorization. This is an optional style
|
||||
// described in the OAuth2 RFC 6749 section 2.3.1.
|
||||
AuthStyleInHeader AuthStyle = 2
|
||||
)
|
||||
|
||||
var (
|
||||
// AccessTypeOnline and AccessTypeOffline are options passed
|
||||
// to the Options.AuthCodeURL method. They modify the
|
||||
|
@ -124,7 +145,7 @@ func SetAuthURLParam(key, value string) AuthCodeOption {
|
|||
//
|
||||
// Opts may include AccessTypeOnline or AccessTypeOffline, as well
|
||||
// as ApprovalForce.
|
||||
// It can also be used to pass the PKCE challange.
|
||||
// It can also be used to pass the PKCE challenge.
|
||||
// See https://www.oauth.com/oauth2-servers/pkce/ for more info.
|
||||
func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
||||
var buf bytes.Buffer
|
||||
|
@ -164,8 +185,7 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
|||
// and when other authorization grant types are not available."
|
||||
// See https://tools.ietf.org/html/rfc6749#section-4.3 for more info.
|
||||
//
|
||||
// The HTTP client to use is derived from the context.
|
||||
// If nil, http.DefaultClient is used.
|
||||
// The provided context optionally controls which HTTP client is used. See the HTTPClient variable.
|
||||
func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) {
|
||||
v := url.Values{
|
||||
"grant_type": {"password"},
|
||||
|
@ -183,8 +203,7 @@ func (c *Config) PasswordCredentialsToken(ctx context.Context, username, passwor
|
|||
// It is used after a resource provider redirects the user back
|
||||
// to the Redirect URI (the URL obtained from AuthCodeURL).
|
||||
//
|
||||
// The HTTP client to use is derived from the context.
|
||||
// If a client is not provided via the context, http.DefaultClient is used.
|
||||
// The provided context optionally controls which HTTP client is used. See the HTTPClient variable.
|
||||
//
|
||||
// The code will be in the *http.Request.FormValue("code"). Before
|
||||
// calling Exchange, be sure to validate FormValue("state").
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package oauth2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -12,7 +13,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/internal"
|
||||
)
|
||||
|
||||
|
@ -118,13 +118,16 @@ func (t *Token) Extra(key string) interface{} {
|
|||
return v
|
||||
}
|
||||
|
||||
// timeNow is time.Now but pulled out as a variable for tests.
|
||||
var timeNow = time.Now
|
||||
|
||||
// expired reports whether the token is expired.
|
||||
// t must be non-nil.
|
||||
func (t *Token) expired() bool {
|
||||
if t.Expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
return t.Expiry.Round(0).Add(-expiryDelta).Before(time.Now())
|
||||
return t.Expiry.Round(0).Add(-expiryDelta).Before(timeNow())
|
||||
}
|
||||
|
||||
// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
|
||||
|
@ -151,7 +154,7 @@ func tokenFromInternal(t *internal.Token) *Token {
|
|||
// This token is then mapped from *internal.Token into an *oauth2.Token which is returned along
|
||||
// with an error..
|
||||
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
|
||||
tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v)
|
||||
tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v, internal.AuthStyle(c.Endpoint.AuthStyle))
|
||||
if err != nil {
|
||||
if rErr, ok := err.(*internal.RetrieveError); ok {
|
||||
return nil, (*RetrieveError)(rErr)
|
||||
|
|
|
@ -124,7 +124,7 @@ func (e *Encoder) Writer(w io.Writer) io.Writer {
|
|||
}
|
||||
|
||||
// ASCIISub is the ASCII substitute character, as recommended by
|
||||
// http://unicode.org/reports/tr36/#Text_Comparison
|
||||
// https://unicode.org/reports/tr36/#Text_Comparison
|
||||
const ASCIISub = '\x1a'
|
||||
|
||||
// Nop is the nop encoding. Its transformed bytes are the same as the source
|
||||
|
|
|
@ -306,6 +306,7 @@ var nameMap = map[string]htmlEncoding{
|
|||
"iso-2022-cn": replacement,
|
||||
"iso-2022-cn-ext": replacement,
|
||||
"iso-2022-kr": replacement,
|
||||
"replacement": replacement,
|
||||
"utf-16be": utf16be,
|
||||
"utf-16": utf16le,
|
||||
"utf-16le": utf16le,
|
||||
|
|
|
@ -34,7 +34,7 @@ package identifier
|
|||
// - http://www.iana.org/assignments/character-sets/character-sets.xhtml
|
||||
// - http://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
|
||||
// - http://www.ietf.org/rfc/rfc2978.txt
|
||||
// - http://www.unicode.org/reports/tr22/
|
||||
// - https://www.unicode.org/reports/tr22/
|
||||
// - http://www.w3.org/TR/encoding/
|
||||
// - https://encoding.spec.whatwg.org/
|
||||
// - https://encoding.spec.whatwg.org/encodings.json
|
||||
|
|
|
@ -884,27 +884,27 @@ const (
|
|||
|
||||
// CESU8 is the MIB identifier with IANA name CESU-8.
|
||||
//
|
||||
// http://www.unicode.org/unicode/reports/tr26
|
||||
// https://www.unicode.org/unicode/reports/tr26
|
||||
CESU8 MIB = 1016
|
||||
|
||||
// UTF32 is the MIB identifier with IANA name UTF-32.
|
||||
//
|
||||
// http://www.unicode.org/unicode/reports/tr19/
|
||||
// https://www.unicode.org/unicode/reports/tr19/
|
||||
UTF32 MIB = 1017
|
||||
|
||||
// UTF32BE is the MIB identifier with IANA name UTF-32BE.
|
||||
//
|
||||
// http://www.unicode.org/unicode/reports/tr19/
|
||||
// https://www.unicode.org/unicode/reports/tr19/
|
||||
UTF32BE MIB = 1018
|
||||
|
||||
// UTF32LE is the MIB identifier with IANA name UTF-32LE.
|
||||
//
|
||||
// http://www.unicode.org/unicode/reports/tr19/
|
||||
// https://www.unicode.org/unicode/reports/tr19/
|
||||
UTF32LE MIB = 1019
|
||||
|
||||
// BOCU1 is the MIB identifier with IANA name BOCU-1.
|
||||
//
|
||||
// http://www.unicode.org/notes/tn6/
|
||||
// https://www.unicode.org/notes/tn6/
|
||||
BOCU1 MIB = 1020
|
||||
|
||||
// Windows30Latin1 is the MIB identifier with IANA name ISO-8859-1-Windows-3.0-Latin-1.
|
||||
|
|
|
@ -10,8 +10,8 @@ package main
|
|||
// go run maketables.go | gofmt > tables.go
|
||||
|
||||
// TODO: Emoji extensions?
|
||||
// http://www.unicode.org/faq/emoji_dingbats.html
|
||||
// http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
|
||||
// https://www.unicode.org/faq/emoji_dingbats.html
|
||||
// https://www.unicode.org/Public/UNIDATA/EmojiSources.txt
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
|
|
@ -145,7 +145,7 @@ func (utf8Decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err e
|
|||
// and consumed in a greater context that implies a certain endianness, use
|
||||
// IgnoreBOM. Otherwise, use ExpectBOM and always produce and consume a BOM.
|
||||
//
|
||||
// In the language of http://www.unicode.org/faq/utf_bom.html#bom10, IgnoreBOM
|
||||
// In the language of https://www.unicode.org/faq/utf_bom.html#bom10, IgnoreBOM
|
||||
// corresponds to "Where the precise type of the data stream is known... the
|
||||
// BOM should not be used" and ExpectBOM corresponds to "A particular
|
||||
// protocol... may require use of the BOM".
|
||||
|
|
|
@ -4,13 +4,13 @@ package language
|
|||
|
||||
// This file contains code common to the maketables.go and the package code.
|
||||
|
||||
// langAliasType is the type of an alias in langAliasMap.
|
||||
type langAliasType int8
|
||||
// AliasType is the type of an alias in AliasMap.
|
||||
type AliasType int8
|
||||
|
||||
const (
|
||||
langDeprecated langAliasType = iota
|
||||
langMacro
|
||||
langLegacy
|
||||
Deprecated AliasType = iota
|
||||
Macro
|
||||
Legacy
|
||||
|
||||
langAliasTypeUnknown langAliasType = -1
|
||||
AliasTypeUnknown AliasType = -1
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package language
|
||||
|
||||
// CompactCoreInfo is a compact integer with the three core tags encoded.
|
||||
type CompactCoreInfo uint32
|
||||
|
||||
// GetCompactCore generates a uint32 value that is guaranteed to be unique for
|
||||
// different language, region, and script values.
|
||||
func GetCompactCore(t Tag) (cci CompactCoreInfo, ok bool) {
|
||||
if t.LangID > langNoIndexOffset {
|
||||
return 0, false
|
||||
}
|
||||
cci |= CompactCoreInfo(t.LangID) << (8 + 12)
|
||||
cci |= CompactCoreInfo(t.ScriptID) << 12
|
||||
cci |= CompactCoreInfo(t.RegionID)
|
||||
return cci, true
|
||||
}
|
||||
|
||||
// Tag generates a tag from c.
|
||||
func (c CompactCoreInfo) Tag() Tag {
|
||||
return Tag{
|
||||
LangID: Language(c >> 20),
|
||||
RegionID: Region(c & 0x3ff),
|
||||
ScriptID: Script(c>>12) & 0xff,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package compact defines a compact representation of language tags.
|
||||
//
|
||||
// Common language tags (at least all for which locale information is defined
|
||||
// in CLDR) are assigned a unique index. Each Tag is associated with such an
|
||||
// ID for selecting language-related resources (such as translations) as well
|
||||
// as one for selecting regional defaults (currency, number formatting, etc.)
|
||||
//
|
||||
// It may want to export this functionality at some point, but at this point
|
||||
// this is only available for use within x/text.
|
||||
package compact // import "golang.org/x/text/internal/language/compact"
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/internal/language"
|
||||
)
|
||||
|
||||
// ID is an integer identifying a single tag.
|
||||
type ID uint16
|
||||
|
||||
func getCoreIndex(t language.Tag) (id ID, ok bool) {
|
||||
cci, ok := language.GetCompactCore(t)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
i := sort.Search(len(coreTags), func(i int) bool {
|
||||
return cci <= coreTags[i]
|
||||
})
|
||||
if i == len(coreTags) || coreTags[i] != cci {
|
||||
return 0, false
|
||||
}
|
||||
return ID(i), true
|
||||
}
|
||||
|
||||
// Parent returns the ID of the parent or the root ID if id is already the root.
|
||||
func (id ID) Parent() ID {
|
||||
return parents[id]
|
||||
}
|
||||
|
||||
// Tag converts id to an internal language Tag.
|
||||
func (id ID) Tag() language.Tag {
|
||||
if int(id) >= len(coreTags) {
|
||||
return specialTags[int(id)-len(coreTags)]
|
||||
}
|
||||
return coreTags[id].Tag()
|
||||
}
|
||||
|
||||
var specialTags []language.Tag
|
||||
|
||||
func init() {
|
||||
tags := strings.Split(specialTagsStr, " ")
|
||||
specialTags = make([]language.Tag, len(tags))
|
||||
for i, t := range tags {
|
||||
specialTags[i] = language.MustParse(t)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Language tag table generator.
|
||||
// Data read from the web.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"golang.org/x/text/internal/gen"
|
||||
"golang.org/x/text/unicode/cldr"
|
||||
)
|
||||
|
||||
var (
|
||||
test = flag.Bool("test",
|
||||
false,
|
||||
"test existing tables; can be used to compare web data with package data.")
|
||||
outputFile = flag.String("output",
|
||||
"tables.go",
|
||||
"output file for generated tables")
|
||||
)
|
||||
|
||||
func main() {
|
||||
gen.Init()
|
||||
|
||||
w := gen.NewCodeWriter()
|
||||
defer w.WriteGoFile("tables.go", "compact")
|
||||
|
||||
fmt.Fprintln(w, `import "golang.org/x/text/internal/language"`)
|
||||
|
||||
b := newBuilder(w)
|
||||
gen.WriteCLDRVersion(w)
|
||||
|
||||
b.writeCompactIndex()
|
||||
}
|
||||
|
||||
type builder struct {
|
||||
w *gen.CodeWriter
|
||||
data *cldr.CLDR
|
||||
supp *cldr.SupplementalData
|
||||
}
|
||||
|
||||
func newBuilder(w *gen.CodeWriter) *builder {
|
||||
r := gen.OpenCLDRCoreZip()
|
||||
defer r.Close()
|
||||
d := &cldr.Decoder{}
|
||||
data, err := d.DecodeZip(r)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
b := builder{
|
||||
w: w,
|
||||
data: data,
|
||||
supp: data.Supplemental(),
|
||||
}
|
||||
return &b
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
// This file generates derivative tables based on the language package itself.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/internal/language"
|
||||
)
|
||||
|
||||
// Compact indices:
|
||||
// Note -va-X variants only apply to localization variants.
|
||||
// BCP variants only ever apply to language.
|
||||
// The only ambiguity between tags is with regions.
|
||||
|
||||
func (b *builder) writeCompactIndex() {
|
||||
// Collect all language tags for which we have any data in CLDR.
|
||||
m := map[language.Tag]bool{}
|
||||
for _, lang := range b.data.Locales() {
|
||||
// We include all locales unconditionally to be consistent with en_US.
|
||||
// We want en_US, even though it has no data associated with it.
|
||||
|
||||
// TODO: put any of the languages for which no data exists at the end
|
||||
// of the index. This allows all components based on ICU to use that
|
||||
// as the cutoff point.
|
||||
// if x := data.RawLDML(lang); false ||
|
||||
// x.LocaleDisplayNames != nil ||
|
||||
// x.Characters != nil ||
|
||||
// x.Delimiters != nil ||
|
||||
// x.Measurement != nil ||
|
||||
// x.Dates != nil ||
|
||||
// x.Numbers != nil ||
|
||||
// x.Units != nil ||
|
||||
// x.ListPatterns != nil ||
|
||||
// x.Collations != nil ||
|
||||
// x.Segmentations != nil ||
|
||||
// x.Rbnf != nil ||
|
||||
// x.Annotations != nil ||
|
||||
// x.Metadata != nil {
|
||||
|
||||
// TODO: support POSIX natively, albeit non-standard.
|
||||
tag := language.Make(strings.Replace(lang, "_POSIX", "-u-va-posix", 1))
|
||||
m[tag] = true
|
||||
// }
|
||||
}
|
||||
|
||||
// TODO: plural rules are also defined for the deprecated tags:
|
||||
// iw mo sh tl
|
||||
// Consider removing these as compact tags.
|
||||
|
||||
// Include locales for plural rules, which uses a different structure.
|
||||
for _, plurals := range b.supp.Plurals {
|
||||
for _, rules := range plurals.PluralRules {
|
||||
for _, lang := range strings.Split(rules.Locales, " ") {
|
||||
m[language.Make(lang)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var coreTags []language.CompactCoreInfo
|
||||
var special []string
|
||||
|
||||
for t := range m {
|
||||
if x := t.Extensions(); len(x) != 0 && fmt.Sprint(x) != "[u-va-posix]" {
|
||||
log.Fatalf("Unexpected extension %v in %v", x, t)
|
||||
}
|
||||
if len(t.Variants()) == 0 && len(t.Extensions()) == 0 {
|
||||
cci, ok := language.GetCompactCore(t)
|
||||
if !ok {
|
||||
log.Fatalf("Locale for non-basic language %q", t)
|
||||
}
|
||||
coreTags = append(coreTags, cci)
|
||||
} else {
|
||||
special = append(special, t.String())
|
||||
}
|
||||
}
|
||||
|
||||
w := b.w
|
||||
|
||||
sort.Slice(coreTags, func(i, j int) bool { return coreTags[i] < coreTags[j] })
|
||||
sort.Strings(special)
|
||||
|
||||
w.WriteComment(`
|
||||
NumCompactTags is the number of common tags. The maximum tag is
|
||||
NumCompactTags-1.`)
|
||||
w.WriteConst("NumCompactTags", len(m))
|
||||
|
||||
fmt.Fprintln(w, "const (")
|
||||
for i, t := range coreTags {
|
||||
fmt.Fprintf(w, "%s ID = %d\n", ident(t.Tag().String()), i)
|
||||
}
|
||||
for i, t := range special {
|
||||
fmt.Fprintf(w, "%s ID = %d\n", ident(t), i+len(coreTags))
|
||||
}
|
||||
fmt.Fprintln(w, ")")
|
||||
|
||||
w.WriteVar("coreTags", coreTags)
|
||||
|
||||
w.WriteConst("specialTagsStr", strings.Join(special, " "))
|
||||
}
|
||||
|
||||
func ident(s string) string {
|
||||
return strings.Replace(s, "-", "", -1) + "Index"
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"golang.org/x/text/internal/gen"
|
||||
"golang.org/x/text/internal/language"
|
||||
"golang.org/x/text/internal/language/compact"
|
||||
"golang.org/x/text/unicode/cldr"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := gen.OpenCLDRCoreZip()
|
||||
defer r.Close()
|
||||
|
||||
d := &cldr.Decoder{}
|
||||
data, err := d.DecodeZip(r)
|
||||
if err != nil {
|
||||
log.Fatalf("DecodeZip: %v", err)
|
||||
}
|
||||
|
||||
w := gen.NewCodeWriter()
|
||||
defer w.WriteGoFile("parents.go", "compact")
|
||||
|
||||
// Create parents table.
|
||||
type ID uint16
|
||||
parents := make([]ID, compact.NumCompactTags)
|
||||
for _, loc := range data.Locales() {
|
||||
tag := language.MustParse(loc)
|
||||
index, ok := compact.FromTag(tag)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
parentIndex := compact.ID(0) // und
|
||||
for p := tag.Parent(); p != language.Und; p = p.Parent() {
|
||||
if x, ok := compact.FromTag(p); ok {
|
||||
parentIndex = x
|
||||
break
|
||||
}
|
||||
}
|
||||
parents[index] = ID(parentIndex)
|
||||
}
|
||||
|
||||
w.WriteComment(`
|
||||
parents maps a compact index of a tag to the compact index of the parent of
|
||||
this tag.`)
|
||||
w.WriteVar("parents", parents)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue