Bump reva deps (#8412)

* bump dependencies

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* bump reva and add config options

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

---------

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2024-02-21 10:20:36 +01:00
committed by GitHub
parent c92ebf4b46
commit 5ed57cc09a
490 changed files with 19128 additions and 11161 deletions
+26 -27
View File
@@ -13,7 +13,7 @@ require (
github.com/cenkalti/backoff v2.2.1+incompatible github.com/cenkalti/backoff v2.2.1+incompatible
github.com/coreos/go-oidc/v3 v3.9.0 github.com/coreos/go-oidc/v3 v3.9.0
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
github.com/cs3org/reva/v2 v2.18.1-0.20240208105019-d5e420d294be github.com/cs3org/reva/v2 v2.18.1-0.20240221074425-cac6c6935c60
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25 github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
@@ -86,7 +86,7 @@ require (
github.com/tus/tusd v1.13.0 github.com/tus/tusd v1.13.0
github.com/urfave/cli/v2 v2.27.1 github.com/urfave/cli/v2 v2.27.1
github.com/xhit/go-simple-mail/v2 v2.16.0 github.com/xhit/go-simple-mail/v2 v2.16.0
go-micro.dev/v4 v4.9.0 go-micro.dev/v4 v4.10.2
go.etcd.io/bbolt v1.3.8 go.etcd.io/bbolt v1.3.8
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0
go.opentelemetry.io/contrib/zpages v0.48.0 go.opentelemetry.io/contrib/zpages v0.48.0
@@ -129,9 +129,9 @@ require (
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 // indirect github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964 // indirect
github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect
github.com/armon/go-radix v1.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go v1.45.1 // indirect github.com/aws/aws-sdk-go v1.45.1 // indirect
github.com/beevik/etree v1.2.0 // indirect github.com/beevik/etree v1.3.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/bits-and-blooms/bitset v1.2.1 // indirect github.com/bits-and-blooms/bitset v1.2.1 // indirect
@@ -160,7 +160,6 @@ require (
github.com/cilium/ebpf v0.9.1 // indirect github.com/cilium/ebpf v0.9.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/cgroups/v3 v3.0.2 // indirect github.com/containerd/cgroups/v3 v3.0.2 // indirect
github.com/coreos/go-oidc v2.2.1+incompatible // indirect
github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cornelk/hashmap v1.0.8 // indirect github.com/cornelk/hashmap v1.0.8 // indirect
@@ -178,7 +177,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/egirna/icap v0.0.0-20181108071049-d5ee18bd70bc // indirect github.com/egirna/icap v0.0.0-20181108071049-d5ee18bd70bc // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/emvi/iso-639-1 v1.0.1 // indirect github.com/emvi/iso-639-1 v1.1.0 // indirect
github.com/evanphx/json-patch/v5 v5.5.0 // indirect github.com/evanphx/json-patch/v5 v5.5.0 // indirect
github.com/fatih/color v1.14.1 // indirect github.com/fatih/color v1.14.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -197,12 +196,12 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-micro/plugins/v4/events/natsjs v1.2.2-0.20231215124540-f7f8d3274bf9 // indirect github.com/go-micro/plugins/v4/events/natsjs v1.2.2-0.20231215124540-f7f8d3274bf9 // indirect
github.com/go-micro/plugins/v4/store/nats-js v1.2.1-0.20231129143103-d72facc652f0 // indirect github.com/go-micro/plugins/v4/store/nats-js v1.2.1-0.20231129143103-d72facc652f0 // indirect
github.com/go-micro/plugins/v4/store/redis v1.2.1-0.20230510195111-07cd57e1bc9d // indirect github.com/go-micro/plugins/v4/store/redis v1.2.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-resty/resty/v2 v2.7.0 // indirect github.com/go-resty/resty/v2 v2.7.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-test/deep v1.1.0 // indirect github.com/go-test/deep v1.1.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
@@ -231,16 +230,16 @@ require (
github.com/hashicorp/consul/api v1.15.2 // indirect github.com/hashicorp/consul/api v1.15.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.3.1 // indirect github.com/hashicorp/go-hclog v1.6.2 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-msgpack v1.1.5 // indirect github.com/hashicorp/go-msgpack v1.1.5 // indirect
github.com/hashicorp/go-plugin v1.4.5 // indirect github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect
github.com/hashicorp/serf v0.10.0 // indirect github.com/hashicorp/serf v0.10.0 // indirect
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect github.com/hashicorp/yamux v0.1.1 // indirect
github.com/huandu/xstrings v1.3.2 // indirect github.com/huandu/xstrings v1.4.0 // indirect
github.com/iancoleman/strcase v0.2.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect
github.com/imdario/mergo v0.3.15 // indirect github.com/imdario/mergo v0.3.15 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
@@ -250,7 +249,7 @@ require (
github.com/juliangruber/go-intersect v1.1.0 // indirect github.com/juliangruber/go-intersect v1.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.5 // indirect github.com/klauspost/compress v1.17.5 // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/libregraph/oidc-go v1.0.0 // indirect github.com/libregraph/oidc-go v1.0.0 // indirect
github.com/longsleep/go-metrics v1.0.0 // indirect github.com/longsleep/go-metrics v1.0.0 // indirect
@@ -259,16 +258,16 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.19 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b // indirect github.com/maxymania/go-system v0.0.0-20170110133659-647cc364bf0b // indirect
github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 // indirect github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 // indirect
github.com/miekg/dns v1.1.50 // indirect github.com/miekg/dns v1.1.50 // indirect
github.com/mileusna/useragent v1.2.1 // indirect github.com/mileusna/useragent v1.3.4 // indirect
github.com/minio/highwayhash v1.0.2 // indirect github.com/minio/highwayhash v1.0.2 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/minio-go/v7 v7.0.42 // indirect github.com/minio/minio-go/v7 v7.0.66 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect github.com/minio/sha256-simd v1.0.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect
@@ -289,7 +288,7 @@ require (
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/cachecontrol v0.1.0 // indirect github.com/pquerna/cachecontrol v0.1.0 // indirect
github.com/prometheus/alertmanager v0.25.1 // indirect github.com/prometheus/alertmanager v0.26.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect
@@ -323,26 +322,26 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.7 // indirect go.etcd.io/etcd/api/v3 v3.5.12 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.12 // indirect
go.etcd.io/etcd/client/v3 v3.5.7 // indirect go.etcd.io/etcd/client/v3 v3.5.12 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib v1.0.0 // indirect go.opentelemetry.io/contrib v1.0.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 // indirect
go.opentelemetry.io/otel/metric v1.23.1 // indirect go.opentelemetry.io/otel/metric v1.23.1 // indirect
go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect
go.uber.org/atomic v1.10.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.8.0 // indirect go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect go.uber.org/zap v1.23.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.17.0 // indirect golang.org/x/sys v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.1 // indirect golang.org/x/tools v0.17.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
+63 -64
View File
@@ -41,7 +41,7 @@ cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMz
cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw=
cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= cloud.google.com/go v0.110.6/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@@ -212,8 +212,8 @@ cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IK
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI=
cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
@@ -871,19 +871,19 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.37.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.37.27/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.45.1 h1:PXuxDZIo/Y9Bvtg2t055+dY4hRwNAEcq6bUMv9fXcjk= github.com/aws/aws-sdk-go v1.45.1 h1:PXuxDZIo/Y9Bvtg2t055+dY4hRwNAEcq6bUMv9fXcjk=
github.com/aws/aws-sdk-go v1.45.1/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go v1.45.1/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/bbalet/stopwords v1.0.0 h1:0TnGycCtY0zZi4ltKoOGRFIlZHv0WqpoIGUsObjztfo= github.com/bbalet/stopwords v1.0.0 h1:0TnGycCtY0zZi4ltKoOGRFIlZHv0WqpoIGUsObjztfo=
github.com/bbalet/stopwords v1.0.0/go.mod h1:sAWrQoDMfqARGIn4s6dp7OW7ISrshUD8IP2q3KoqPjc= github.com/bbalet/stopwords v1.0.0/go.mod h1:sAWrQoDMfqARGIn4s6dp7OW7ISrshUD8IP2q3KoqPjc=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beevik/etree v1.2.0 h1:l7WETslUG/T+xOPs47dtd6jov2Ii/8/OjCldk5fYfQw= github.com/beevik/etree v1.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU=
github.com/beevik/etree v1.2.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= github.com/beevik/etree v1.3.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -940,6 +940,8 @@ github.com/bombsimon/logrusr/v3 v3.1.0/go.mod h1:PksPPgSFEL2I52pla2glgCyyd2OqOHA
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q=
@@ -995,8 +997,6 @@ github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKk
github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
@@ -1019,8 +1019,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME= github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY=
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/reva/v2 v2.18.1-0.20240208105019-d5e420d294be h1:vljLn7q9NPfiynhzGBRD1sv8qhYGd6SZOHgvzv+Q9iA= github.com/cs3org/reva/v2 v2.18.1-0.20240221074425-cac6c6935c60 h1:cx4hWeWKYnIa8m5z9JncRc+lt/+RB3mwg/kZWBbDPAc=
github.com/cs3org/reva/v2 v2.18.1-0.20240208105019-d5e420d294be/go.mod h1:GCN3g6uYE0Nvd31dGlhaGGyUviUfbG2NkecPRv5oSc4= github.com/cs3org/reva/v2 v2.18.1-0.20240221074425-cac6c6935c60/go.mod h1:GRUrOp5HbFVwZTgR9bVrMZ/MvVy+Jhxw1PdMmhhKP9E=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
@@ -1071,8 +1071,8 @@ github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcej
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/emvi/iso-639-1 v1.0.1 h1:4s6P8Uxc/RDkwCpxAr4NHOT/15a1swy9IQkB8PJCWVI= github.com/emvi/iso-639-1 v1.1.0 h1:EhZiYVA+ysa/b7+0T2DD9hcX7E/5sh4o1KyDAIPu7VE=
github.com/emvi/iso-639-1 v1.0.1/go.mod h1:mghC4MDFyszxzH98ujf/K5whvB6B0nV4qCa5u94dP84= github.com/emvi/iso-639-1 v1.1.0/go.mod h1:CSA53/Tx0xF9bk2DEA0Mr0wTdIxq7pqoVZgBOfoL5GI=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -1216,8 +1216,8 @@ github.com/go-micro/plugins/v4/server/http v1.2.2 h1:UK2/09AU0zV3wHELuR72TZzVU2v
github.com/go-micro/plugins/v4/server/http v1.2.2/go.mod h1:YuAjaSPxcn3LI8j2FUsqx0Rxunrj4YwDV41Ax76rLl0= github.com/go-micro/plugins/v4/server/http v1.2.2/go.mod h1:YuAjaSPxcn3LI8j2FUsqx0Rxunrj4YwDV41Ax76rLl0=
github.com/go-micro/plugins/v4/store/nats-js v1.2.1-0.20231129143103-d72facc652f0 h1:Qa1EBQ9UyCGecFAJQovl/MHGnvbcvDaM3qUoAG5Lnvk= github.com/go-micro/plugins/v4/store/nats-js v1.2.1-0.20231129143103-d72facc652f0 h1:Qa1EBQ9UyCGecFAJQovl/MHGnvbcvDaM3qUoAG5Lnvk=
github.com/go-micro/plugins/v4/store/nats-js v1.2.1-0.20231129143103-d72facc652f0/go.mod h1:aCRl8JQmqIaonOl88nFPY/BOQnHPVHY9ngStzLkXnYk= github.com/go-micro/plugins/v4/store/nats-js v1.2.1-0.20231129143103-d72facc652f0/go.mod h1:aCRl8JQmqIaonOl88nFPY/BOQnHPVHY9ngStzLkXnYk=
github.com/go-micro/plugins/v4/store/redis v1.2.1-0.20230510195111-07cd57e1bc9d h1:HQoDDVyMfdkrgXNo03ZY4vzhoOXMDZVZ4SnpBDVID6E= github.com/go-micro/plugins/v4/store/redis v1.2.1 h1:d9kwr9bSpoK9vkHkqcv+isQUbgBCHpfwCV57pcAPS6c=
github.com/go-micro/plugins/v4/store/redis v1.2.1-0.20230510195111-07cd57e1bc9d/go.mod h1:MbCG0YiyPqETTtm7uHFmxQNCaW1o9hBoYtFwhbVjLUg= github.com/go-micro/plugins/v4/store/redis v1.2.1/go.mod h1:MbCG0YiyPqETTtm7uHFmxQNCaW1o9hBoYtFwhbVjLUg=
github.com/go-micro/plugins/v4/transport/grpc v1.1.0 h1:mXfDYfFQLnVDzjGY3o84oe4prfux9h8txsnA19dKsj8= github.com/go-micro/plugins/v4/transport/grpc v1.1.0 h1:mXfDYfFQLnVDzjGY3o84oe4prfux9h8txsnA19dKsj8=
github.com/go-micro/plugins/v4/transport/grpc v1.1.0/go.mod h1:J5xMp70xXZzm8yafICrDrWaUDd8Gwy8vt0xif7NcOPg= github.com/go-micro/plugins/v4/transport/grpc v1.1.0/go.mod h1:J5xMp70xXZzm8yafICrDrWaUDd8Gwy8vt0xif7NcOPg=
github.com/go-micro/plugins/v4/wrapper/breaker/gobreaker v1.2.0 h1:EQj4l7fuOSz8ueUYhFlpZPp9+tN4JeONL32ARRKXW/U= github.com/go-micro/plugins/v4/wrapper/breaker/gobreaker v1.2.0 h1:EQj4l7fuOSz8ueUYhFlpZPp9+tN4JeONL32ARRKXW/U=
@@ -1243,8 +1243,8 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
@@ -1474,8 +1474,8 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.3.1 h1:vDwF1DFNZhntP4DAjuTpOw3uEgMUpXh1pB5fW9DqHpo= github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I=
github.com/hashicorp/go-hclog v1.3.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
@@ -1488,8 +1488,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
@@ -1523,13 +1523,14 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hashicorp/serf v0.10.0 h1:89qvvpfMQnz6c2y4pv7j2vUUmeT1+5TSZMexuTbtsPs= github.com/hashicorp/serf v0.10.0 h1:89qvvpfMQnz6c2y4pv7j2vUUmeT1+5TSZMexuTbtsPs=
github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw= github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw=
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
@@ -1547,8 +1548,8 @@ github.com/jellydator/ttlcache/v2 v2.11.1/go.mod h1:RtE5Snf0/57e+2cLWFYWCCsLas2H
github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE=
github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@@ -1591,10 +1592,9 @@ github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHU
github.com/klauspost/compress v1.17.5 h1:d4vBd+7CHydUqpFBgUEKkSdtSugf9YFmSkvUYPquI5E= github.com/klauspost/compress v1.17.5 h1:d4vBd+7CHydUqpFBgUEKkSdtSugf9YFmSkvUYPquI5E=
github.com/klauspost/compress v1.17.5/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.5/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kobergj/plugins/v4/store/nats-js-kv v0.0.0-20231207143248-4d424e3ae348 h1:Czv6AW9Suj6npWd5BLZjobdD78c2RdzBeKBgkq3jYZk= github.com/kobergj/plugins/v4/store/nats-js-kv v0.0.0-20231207143248-4d424e3ae348 h1:Czv6AW9Suj6npWd5BLZjobdD78c2RdzBeKBgkq3jYZk=
github.com/kobergj/plugins/v4/store/nats-js-kv v0.0.0-20231207143248-4d424e3ae348/go.mod h1:Goi4eJ9SrKkxE6NsAVqBVNxfQFbwb7UbyII6743ldgM= github.com/kobergj/plugins/v4/store/nats-js-kv v0.0.0-20231207143248-4d424e3ae348/go.mod h1:Goi4eJ9SrKkxE6NsAVqBVNxfQFbwb7UbyII6743ldgM=
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
@@ -1673,8 +1673,8 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -1691,18 +1691,18 @@ github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/mileusna/useragent v1.2.1 h1:p3RJWhi3LfuI6BHdddojREyK3p6qX67vIfOVMnUIVr0= github.com/mileusna/useragent v1.3.4 h1:MiuRRuvGjEie1+yZHO88UBYg8YBC/ddF6T7F56i3PCk=
github.com/mileusna/useragent v1.2.1/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= github.com/mileusna/useragent v1.3.4/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.42 h1:fP56plNR/Tkw/+Xczw9NL5TGxe5gJDvgd8LidNR3BEI= github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
github.com/minio/minio-go/v7 v7.0.42/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw= github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
@@ -1830,8 +1830,8 @@ github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prY
github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/alertmanager v0.25.1 h1:LGBNMspOfv8h7brb+LWj2wnwBCg2ZuuKWTh6CAVw2/Y= github.com/prometheus/alertmanager v0.26.0 h1:uOMJWfIwJguc3NaM3appWNbbrh6G/OjvaHMk22aBBYc=
github.com/prometheus/alertmanager v0.25.1/go.mod h1:MEZ3rFVHqKZsw7IcNS/m4AWZeXThmJhumpiWR4eHU/w= github.com/prometheus/alertmanager v0.26.0/go.mod h1:rVcnARltVjavgVaNnmevxK7kOn7IZavyf0KNgHkbEpU=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
@@ -2008,8 +2008,8 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG
github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo= github.com/thanhpk/randstr v1.0.6 h1:psAOktJFD4vV9NEVb3qkhRSMvYh4ORRaj1+w/hn4B+o=
github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U= github.com/thanhpk/randstr v1.0.6/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
github.com/thejerf/suture/v4 v4.0.2 h1:VxIH/J8uYvqJY1+9fxi5GBfGRkRZ/jlSOP6x9HijFQc= github.com/thejerf/suture/v4 v4.0.2 h1:VxIH/J8uYvqJY1+9fxi5GBfGRkRZ/jlSOP6x9HijFQc=
github.com/thejerf/suture/v4 v4.0.2/go.mod h1:g0e8vwskm9tI0jRjxrnA6lSr0q6OfPdWJVX7G5bVWRs= github.com/thejerf/suture/v4 v4.0.2/go.mod h1:g0e8vwskm9tI0jRjxrnA6lSr0q6OfPdWJVX7G5bVWRs=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
@@ -2068,17 +2068,17 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go-micro.dev/v4 v4.9.0 h1:pd1CpqMT9hA47jSmX8mfdGK865PkMh95Rwj5RdfqPqE= go-micro.dev/v4 v4.10.2 h1:GWQf1+FcAiMf1yca3P09RNjB31Xtk0C5HiKHSpq/2qA=
go-micro.dev/v4 v4.9.0/go.mod h1:Ju8HrZ5hQSF+QguZ2QUs9Kbe42MHP1tJa/fpP5g07Cs= go-micro.dev/v4 v4.10.2/go.mod h1:RV2AolXjTAil9Xm82QCMo1gknuZwD61oMUH14wJpECk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY= go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c=
go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA= go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4=
go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg= go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A=
go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY= go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4=
go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4= go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg=
go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw= go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -2092,8 +2092,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib v1.0.0 h1:khwDCxdSspjOLmFnvMuSHd/5rPzbTx0+l6aURwtQdfE= go.opentelemetry.io/contrib v1.0.0 h1:khwDCxdSspjOLmFnvMuSHd/5rPzbTx0+l6aURwtQdfE=
go.opentelemetry.io/contrib v1.0.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= go.opentelemetry.io/contrib v1.0.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 h1:P+/g8GpuJGYbOp2tAdKrIPUX9JO02q8Q0YNlHolpibA=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA=
go.opentelemetry.io/contrib/zpages v0.48.0 h1:7vnqarYRu2VYvUUwrLWXRk5HqPuCbtuZdgR4eaVeP3Y= go.opentelemetry.io/contrib/zpages v0.48.0 h1:7vnqarYRu2VYvUUwrLWXRk5HqPuCbtuZdgR4eaVeP3Y=
@@ -2123,8 +2123,8 @@ go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7e
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
@@ -2186,8 +2186,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -2477,7 +2477,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -2626,8 +2625,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -2862,8 +2861,8 @@ google.golang.org/genproto v0.0.0-20230629202037-9506855d4529/go.mod h1:xZnkP7mR
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y= google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108= google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108=
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8=
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac h1:ZL/Teoy/ZGnzyrqK/Optxxp2pmVh+fmJ97slxSRyzUg= google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUbUhquq98xey1slwvuVJPosdBqYJlU=
google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8=
google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig=
+2 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/ocis-pkg/shared"
"go-micro.dev/v4/client" "go-micro.dev/v4/client"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
) )
// ClientOptions represent options (e.g. tls settings) for the grpc clients // ClientOptions represent options (e.g. tls settings) for the grpc clients
@@ -45,7 +46,7 @@ func WithTraceProvider(tp trace.TracerProvider) ClientOption {
if tp != nil { if tp != nil {
o.tp = tp o.tp = tp
} else { } else {
o.tp = trace.NewNoopTracerProvider() o.tp = noop.NewTracerProvider()
} }
} }
} }
+2
View File
@@ -21,6 +21,8 @@ type Config struct {
SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"SHARING_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token."` SkipUserGroupsInToken bool `yaml:"skip_user_groups_in_token" env:"SHARING_SKIP_USER_GROUPS_IN_TOKEN" desc:"Disables the loading of user's group memberships from the reva access token."`
EnableResharing bool `yaml:"enable_resharing" env:"OCIS_ENABLE_RESHARING;SHARING_ENABLE_RESHARING" desc:"Changing this value is NOT supported. Enables the support for resharing."`
UserSharingDriver string `yaml:"user_sharing_driver" env:"SHARING_USER_DRIVER" desc:"Driver to be used to persist shares. Supported values are 'jsoncs3', 'json', 'cs3' (deprecated) and 'owncloudsql'."` UserSharingDriver string `yaml:"user_sharing_driver" env:"SHARING_USER_DRIVER" desc:"Driver to be used to persist shares. Supported values are 'jsoncs3', 'json', 'cs3' (deprecated) and 'owncloudsql'."`
UserSharingDrivers UserSharingDrivers `yaml:"user_sharing_drivers"` UserSharingDrivers UserSharingDrivers `yaml:"user_sharing_drivers"`
PublicSharingDriver string `yaml:"public_sharing_driver" env:"SHARING_PUBLIC_DRIVER" desc:"Driver to be used to persist public shares. Supported values are 'jsoncs3', 'json' and 'cs3' (deprecated)."` PublicSharingDriver string `yaml:"public_sharing_driver" env:"SHARING_PUBLIC_DRIVER" desc:"Driver to be used to persist public shares. Supported values are 'jsoncs3', 'json' and 'cs3' (deprecated)."`
@@ -35,6 +35,7 @@ func DefaultConfig() *config.Config {
Name: "sharing", Name: "sharing",
}, },
Reva: shared.DefaultRevaConfig(), Reva: shared.DefaultRevaConfig(),
EnableResharing: true,
UserSharingDriver: "jsoncs3", UserSharingDriver: "jsoncs3",
UserSharingDrivers: config.UserSharingDrivers{ UserSharingDrivers: config.UserSharingDrivers{
JSON: config.UserSharingJSONDriver{ JSON: config.UserSharingJSONDriver{
@@ -86,6 +86,7 @@ func SharingConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string]
}, },
}, },
}, },
"disable_resharing": !cfg.EnableResharing,
}, },
"publicshareprovider": map[string]interface{}{ "publicshareprovider": map[string]interface{}{
"gateway_addr": cfg.Reva.Address, "gateway_addr": cfg.Reva.Address,
+25 -8
View File
@@ -84,6 +84,17 @@ type HTTPConfig struct {
Namespace string `yaml:"-"` Namespace string `yaml:"-"`
Protocol string `yaml:"protocol" env:"STORAGE_USERS_HTTP_PROTOCOL" desc:"The transport protocol of the HTTP service."` Protocol string `yaml:"protocol" env:"STORAGE_USERS_HTTP_PROTOCOL" desc:"The transport protocol of the HTTP service."`
Prefix string Prefix string
CORS CORS `yaml:"cors"`
}
// CORS defines the available cors configuration.
type CORS struct {
AllowedOrigins []string `yaml:"allow_origins" env:"OCIS_CORS_ALLOW_ORIGINS;STORAGE_USERS_CORS_ALLOW_ORIGINS" desc:"A list of allowed CORS origins. See following chapter for more details: *Access-Control-Allow-Origin* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin. See the Environment Variable Types description for more details."`
AllowedMethods []string `yaml:"allow_methods" env:"OCIS_CORS_ALLOW_METHODS;STORAGE_USERS_CORS_ALLOW_METHODS" desc:"A list of allowed CORS methods. See following chapter for more details: *Access-Control-Request-Method* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method. See the Environment Variable Types description for more details."`
AllowedHeaders []string `yaml:"allow_headers" env:"OCIS_CORS_ALLOW_HEADERS;STORAGE_USERS_CORS_ALLOW_HEADERS" desc:"A list of allowed CORS headers. See following chapter for more details: *Access-Control-Request-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers. See the Environment Variable Types description for more details."`
AllowCredentials bool `yaml:"allow_credentials" env:"OCIS_CORS_ALLOW_CREDENTIALS;STORAGE_USERS_CORS_ALLOW_CREDENTIALS" desc:"Allow credentials for CORS.See following chapter for more details: *Access-Control-Allow-Credentials* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials."`
ExposedHeaders []string `yaml:"expose_headers" env:"OCIS_CORS_EXPOSE_HEADERS;STORAGE_USERS_CORS_EXPOSE_HEADERS" desc:"A list of exposed CORS headers. See following chapter for more details: *Access-Control-Expose-Headers* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers. See the Environment Variable Types description for more details."`
MaxAge uint `yaml:"max_age" env:"OCIS_CORS_MAX_AGE;STORAGE_USERS_CORS_MAX_AGE" desc:"The max cache duration of preflight headers. See following chapter for more details: *Access-Control-Max-Age* at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age. See the Environment Variable Types description for more details."`
} }
// Drivers combine all storage driver configurations // Drivers combine all storage driver configurations
@@ -132,14 +143,20 @@ type S3NGDriver struct {
Propagator string `yaml:"propagator" env:"OCIS_DECOMPOSEDFS_PROPAGATOR;STORAGE_USERS_S3NG_PROPAGATOR" desc:"The propagator used for decomposedfs. At the moment, only 'sync' is fully supported, 'async' is available as an experimental option."` Propagator string `yaml:"propagator" env:"OCIS_DECOMPOSEDFS_PROPAGATOR;STORAGE_USERS_S3NG_PROPAGATOR" desc:"The propagator used for decomposedfs. At the moment, only 'sync' is fully supported, 'async' is available as an experimental option."`
AsyncPropagatorOptions AsyncPropagatorOptions `yaml:"async_propagator_options"` AsyncPropagatorOptions AsyncPropagatorOptions `yaml:"async_propagator_options"`
// Root is the absolute path to the location of the data // Root is the absolute path to the location of the data
Root string `yaml:"root" env:"STORAGE_USERS_S3NG_ROOT" desc:"The directory where the filesystem storage will store metadata for blobs. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage/users."` Root string `yaml:"root" env:"STORAGE_USERS_S3NG_ROOT" desc:"The directory where the filesystem storage will store metadata for blobs. If not defined, the root directory derives from $OCIS_BASE_DATA_PATH:/storage/users."`
UserLayout string `yaml:"user_layout" env:"STORAGE_USERS_S3NG_USER_LAYOUT" desc:"Template string for the user storage layout in the user directory."` UserLayout string `yaml:"user_layout" env:"STORAGE_USERS_S3NG_USER_LAYOUT" desc:"Template string for the user storage layout in the user directory."`
PermissionsEndpoint string `yaml:"permissions_endpoint" env:"STORAGE_USERS_PERMISSION_ENDPOINT;STORAGE_USERS_S3NG_PERMISSIONS_ENDPOINT" desc:"Endpoint of the permissions service. The endpoints can differ for 'ocis' and 's3ng'."` PermissionsEndpoint string `yaml:"permissions_endpoint" env:"STORAGE_USERS_PERMISSION_ENDPOINT;STORAGE_USERS_S3NG_PERMISSIONS_ENDPOINT" desc:"Endpoint of the permissions service. The endpoints can differ for 'ocis' and 's3ng'."`
Region string `yaml:"region" env:"STORAGE_USERS_S3NG_REGION" desc:"Region of the S3 bucket."` Region string `yaml:"region" env:"STORAGE_USERS_S3NG_REGION" desc:"Region of the S3 bucket."`
AccessKey string `yaml:"access_key" env:"STORAGE_USERS_S3NG_ACCESS_KEY" desc:"Access key for the S3 bucket."` AccessKey string `yaml:"access_key" env:"STORAGE_USERS_S3NG_ACCESS_KEY" desc:"Access key for the S3 bucket."`
SecretKey string `yaml:"secret_key" env:"STORAGE_USERS_S3NG_SECRET_KEY" desc:"Secret key for the S3 bucket."` SecretKey string `yaml:"secret_key" env:"STORAGE_USERS_S3NG_SECRET_KEY" desc:"Secret key for the S3 bucket."`
Endpoint string `yaml:"endpoint" env:"STORAGE_USERS_S3NG_ENDPOINT" desc:"Endpoint for the S3 bucket."` Endpoint string `yaml:"endpoint" env:"STORAGE_USERS_S3NG_ENDPOINT" desc:"Endpoint for the S3 bucket."`
Bucket string `yaml:"bucket" env:"STORAGE_USERS_S3NG_BUCKET" desc:"Name of the S3 bucket."` Bucket string `yaml:"bucket" env:"STORAGE_USERS_S3NG_BUCKET" desc:"Name of the S3 bucket."`
DisableContentSha256 bool `yaml:"put_object_disable_content_sha254" env:"STORAGE_USERS_S3NG_PUT_OBJECT_DISABLE_CONTENT_SHA256" desc:"Disable sending content sha256 when copying objects to S3."`
DisableMultipart bool `yaml:"put_object_disable_multipart" env:"STORAGE_USERS_S3NG_PUT_OBJECT_DISABLE_MULTIPART" desc:"Disable multipart uploads when copying objects to S3"`
SendContentMd5 bool `yaml:"put_object_send_content_md5" env:"STORAGE_USERS_S3NG_PUT_OBJECT_SEND_CONTENT_MD5" desc:"Send a Content-MD5 header when copying objects to S3."`
ConcurrentStreamParts bool `yaml:"put_object_concurrent_stream_parts" env:"STORAGE_USERS_S3NG_PUT_OBJECT_CONCURRENT_STREAM_PARTS" desc:"Always precreate parts when copying objects to S3."`
NumThreads uint `yaml:"put_object_num_threads" env:"STORAGE_USERS_S3NG_PUT_OBJECT_NUM_THREADS" desc:"Number of concurrent uploads to use when copying objects to S3."`
PartSize uint64 `yaml:"put_object_part_size" env:"STORAGE_USERS_S3NG_PUT_OBJECT_PART_SIZE" desc:"Part size for concurrent uploads to S3."`
// PersonalSpaceAliasTemplate contains the template used to construct // PersonalSpaceAliasTemplate contains the template used to construct
// the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"` // the personal space alias, eg: `"{{.SpaceType}}/{{.User.Username | lower}}"`
PersonalSpaceAliasTemplate string `yaml:"personalspacealias_template" env:"STORAGE_USERS_S3NG_PERSONAL_SPACE_ALIAS_TEMPLATE" desc:"Template string to construct personal space aliases."` PersonalSpaceAliasTemplate string `yaml:"personalspacealias_template" env:"STORAGE_USERS_S3NG_PERSONAL_SPACE_ALIAS_TEMPLATE" desc:"Template string to construct personal space aliases."`
@@ -37,6 +37,49 @@ func DefaultConfig() *config.Config {
Namespace: "com.owncloud.web", Namespace: "com.owncloud.web",
Protocol: "tcp", Protocol: "tcp",
Prefix: "data", Prefix: "data",
CORS: config.CORS{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{
"POST",
"HEAD",
"PATCH",
"OPTIONS",
"GET",
"DELETE",
},
AllowedHeaders: []string{
"Authorization",
"Origin",
"X-Requested-With",
"X-Request-Id",
"X-HTTP-Method-Override",
"Content-Type",
"Upload-Length",
"Upload-Offset",
"Tus-Resumable",
"Upload-Metadata",
"Upload-Defer-Length",
"Upload-Concat",
"Upload-Incomplete",
"Upload-Draft-Interop-Version",
},
AllowCredentials: true,
ExposedHeaders: []string{
"Upload-Offset",
"Location",
"Upload-Length",
"Tus-Version",
"Tus-Resumable",
"Tus-Max-Size",
"Tus-Extension",
"Upload-Metadata",
"Upload-Defer-Length",
"Upload-Concat",
"Upload-Incomplete",
"Upload-Draft-Interop-Version",
},
MaxAge: 86400,
},
}, },
Service: config.Service{ Service: config.Service{
Name: "storage-users", Name: "storage-users",
@@ -69,6 +112,9 @@ func DefaultConfig() *config.Config {
ShareFolder: "/Shares", ShareFolder: "/Shares",
UserLayout: "{{.Id.OpaqueId}}", UserLayout: "{{.Id.OpaqueId}}",
Region: "default", Region: "default",
SendContentMd5: true,
ConcurrentStreamParts: true,
NumThreads: 4,
PersonalSpaceAliasTemplate: "{{.SpaceType}}/{{.User.Username | lower}}", PersonalSpaceAliasTemplate: "{{.SpaceType}}/{{.User.Username | lower}}",
GeneralSpaceAliasTemplate: "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}", GeneralSpaceAliasTemplate: "{{.SpaceType}}/{{.SpaceName | replace \" \" \"-\" | lower}}",
PermissionsEndpoint: "com.owncloud.api.settings", PermissionsEndpoint: "com.owncloud.api.settings",
@@ -2,6 +2,9 @@
package revaconfig package revaconfig
import ( import (
"strconv"
"strings"
"github.com/owncloud/ocis/v2/services/storage-users/pkg/config" "github.com/owncloud/ocis/v2/services/storage-users/pkg/config"
) )
@@ -74,6 +77,31 @@ func StorageUsersConfigFromStruct(cfg *config.Config) map[string]interface{} {
"nats_enable_tls": cfg.Events.EnableTLS, "nats_enable_tls": cfg.Events.EnableTLS,
"nats_username": cfg.Events.AuthUsername, "nats_username": cfg.Events.AuthUsername,
"nats_password": cfg.Events.AuthPassword, "nats_password": cfg.Events.AuthPassword,
"data_txs": map[string]interface{}{
"simple": map[string]interface{}{
"cache_store": "noop",
"cache_database": "system",
"cache_table": "stat",
},
"spaces": map[string]interface{}{
"cache_store": "noop",
"cache_database": "system",
"cache_table": "stat",
},
"tus": map[string]interface{}{
"cache_store": "noop",
"cache_database": "system",
"cache_table": "stat",
"cors_enabled": true,
// allow_origin is configured as a regex in tusd, so we concatenate the configured values into a regex
"cors_allow_origin": "(" + strings.ReplaceAll(strings.Join(cfg.HTTP.CORS.AllowedOrigins, "|"), "*", ".*") + ")",
"cors_allow_credentials": cfg.HTTP.CORS.AllowCredentials,
"cors_allow_methods": strings.Join(cfg.HTTP.CORS.AllowedMethods, ","),
"cors_allow_headers": strings.Join(cfg.HTTP.CORS.AllowedHeaders, ","),
"cors_max_age": strconv.FormatUint(uint64(cfg.HTTP.CORS.MaxAge), 10),
"cors_expose_headers": strings.Join(cfg.HTTP.CORS.ExposedHeaders, ","),
},
},
}, },
}, },
}, },
@@ -243,6 +243,12 @@ func S3NG(cfg *config.Config) map[string]interface{} {
"s3.secret_key": cfg.Drivers.S3NG.SecretKey, "s3.secret_key": cfg.Drivers.S3NG.SecretKey,
"s3.endpoint": cfg.Drivers.S3NG.Endpoint, "s3.endpoint": cfg.Drivers.S3NG.Endpoint,
"s3.bucket": cfg.Drivers.S3NG.Bucket, "s3.bucket": cfg.Drivers.S3NG.Bucket,
"s3.disable_content_sha254": cfg.Drivers.S3NG.DisableContentSha256,
"s3.disable_multipart": cfg.Drivers.S3NG.DisableMultipart,
"s3.send_content_md5": cfg.Drivers.S3NG.SendContentMd5,
"s3.concurrent_stream_parts": cfg.Drivers.S3NG.ConcurrentStreamParts,
"s3.num_threads": cfg.Drivers.S3NG.NumThreads,
"s3.part_size": cfg.Drivers.S3NG.PartSize,
"max_acquire_lock_cycles": cfg.Drivers.S3NG.MaxAcquireLockCycles, "max_acquire_lock_cycles": cfg.Drivers.S3NG.MaxAcquireLockCycles,
"lock_cycle_duration_factor": cfg.Drivers.S3NG.LockCycleDurationFactor, "lock_cycle_duration_factor": cfg.Drivers.S3NG.LockCycleDurationFactor,
"max_concurrency": cfg.Drivers.S3NG.MaxConcurrency, "max_concurrency": cfg.Drivers.S3NG.MaxConcurrency,
@@ -97,11 +97,6 @@ cannot share a folder with create permission
- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:118](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L118) - [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:118](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L118)
- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L130) - [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:130](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L130)
#### [OCS error message for attempting to access share via share id as an unauthorized user is not informative](https://github.com/owncloud/ocis/issues/1233)
- [coreApiShareOperationsToShares1/gettingShares.feature:144](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/gettingShares.feature#L144)
- [coreApiShareOperationsToShares1/gettingShares.feature:145](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares1/gettingShares.feature#L145)
#### [Public link enforce permissions](https://github.com/owncloud/ocis/issues/1269) #### [Public link enforce permissions](https://github.com/owncloud/ocis/issues/1269)
- [coreApiSharePublicLink1/createPublicLinkShare.feature:288](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L288) - [coreApiSharePublicLink1/createPublicLinkShare.feature:288](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L288)
@@ -182,23 +177,6 @@ cannot share a folder with create permission
- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:207](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L207) - [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:207](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L207)
- [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:208](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L208) - [coreApiShareManagementBasicToShares/deleteShareFromShares.feature:208](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareManagementBasicToShares/deleteShareFromShares.feature#L208)
### User Management
User and group management features
#### [incorrect ocs(v2) status value when getting info of share that does not exist should be 404, gives 998](https://github.com/owncloud/product/issues/250)
_ocs: api compatibility, return correct status code_
- [coreApiShareOperationsToShares2/shareAccessByID.feature:48](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L48)
- [coreApiShareOperationsToShares2/shareAccessByID.feature:49](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L49)
- [coreApiShareOperationsToShares2/shareAccessByID.feature:50](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L50)
- [coreApiShareOperationsToShares2/shareAccessByID.feature:51](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L51)
- [coreApiShareOperationsToShares2/shareAccessByID.feature:52](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L52)
- [coreApiShareOperationsToShares2/shareAccessByID.feature:53](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L53)
- [coreApiShareOperationsToShares2/shareAccessByID.feature:54](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L54)
- [coreApiShareOperationsToShares2/shareAccessByID.feature:55](https://github.com/owncloud/ocis/blob/master/tests/acceptance/features/coreApiShareOperationsToShares2/shareAccessByID.feature#L55)
### Other ### Other
API, search, favorites, config, capabilities, not existing endpoints, CORS and others API, search, favorites, config, capabilities, not existing endpoints, CORS and others
+19 -20
View File
@@ -454,27 +454,26 @@ func IsCreditCard(str string) bool {
if !rxCreditCard.MatchString(sanitized) { if !rxCreditCard.MatchString(sanitized) {
return false return false
} }
var sum int64
var digit string number, _ := ToInt(sanitized)
var tmpNum int64 number, lastDigit := number / 10, number % 10
var shouldDouble bool
for i := len(sanitized) - 1; i >= 0; i-- {
digit = sanitized[i:(i + 1)]
tmpNum, _ = ToInt(digit)
if shouldDouble {
tmpNum *= 2
if tmpNum >= 10 {
sum += (tmpNum % 10) + 1
} else {
sum += tmpNum
}
} else {
sum += tmpNum
}
shouldDouble = !shouldDouble
}
return sum%10 == 0 var sum int64
for i:=0; number > 0; i++ {
digit := number % 10
if i % 2 == 0 {
digit *= 2
if digit > 9 {
digit -= 9
}
}
sum += digit
number = number / 10
}
return (sum + lastDigit) % 10 == 0
} }
// IsISBN10 checks if the string is an ISBN version 10. // IsISBN10 checks if the string is an ISBN version 10.
+1
View File
@@ -10,3 +10,4 @@ Earncef Sequeira (earncef)
Gabriel de Labachelerie (wuzuf) Gabriel de Labachelerie (wuzuf)
Martin Dosch (mdosch) Martin Dosch (mdosch)
Hugo Wetterberg (hugowetterberg) Hugo Wetterberg (hugowetterberg)
Tobias Theel (nerzal)
+1
View File
@@ -1,4 +1,5 @@
[![GoDoc](https://godoc.org/github.com/beevik/etree?status.svg)](https://godoc.org/github.com/beevik/etree) [![GoDoc](https://godoc.org/github.com/beevik/etree?status.svg)](https://godoc.org/github.com/beevik/etree)
[![Go](https://github.com/beevik/etree/actions/workflows/go.yml/badge.svg)](https://github.com/beevik/etree/actions/workflows/go.yml)
etree etree
===== =====
+9
View File
@@ -1,3 +1,12 @@
Release v1.3.0
==============
**New Features**
* Add support for double-quotes in filter path queries.
* Add `PreserveDuplicateAttrs` to `ReadSettings`.
* Add `ReindexChildren` to `Element`.
Release v1.2.0 Release v1.2.0
============== ==============
+23 -7
View File
@@ -46,6 +46,10 @@ type ReadSettings struct {
// false. // false.
PreserveCData bool PreserveCData bool
// When an element has two or more attributes with the same name,
// preserve them instead of keeping only one. Default: false.
PreserveDuplicateAttrs bool
// Entity to be passed to standard xml.Decoder. Default: nil. // Entity to be passed to standard xml.Decoder. Default: nil.
Entity map[string]string Entity map[string]string
} }
@@ -549,6 +553,15 @@ func (e *Element) name() string {
return e.Tag return e.Tag
} }
// ReindexChildren recalculates the index values of the element's child
// tokens. This is necessary only if you have manually manipulated the
// element's `Child` array.
func (e *Element) ReindexChildren() {
for i := 0; i < len(e.Child); i++ {
e.Child[i].setIndex(i)
}
}
// Text returns all character data immediately following the element's opening // Text returns all character data immediately following the element's opening
// tag. // tag.
func (e *Element) Text() string { func (e *Element) Text() string {
@@ -827,7 +840,7 @@ func (e *Element) readFrom(ri io.Reader, settings ReadSettings) (n int64, err er
case xml.StartElement: case xml.StartElement:
e := newElement(t.Name.Space, t.Name.Local, top) e := newElement(t.Name.Space, t.Name.Local, top)
for _, a := range t.Attr { for _, a := range t.Attr {
e.createAttr(a.Name.Space, a.Name.Local, a.Value, e) e.createAttr(a.Name.Space, a.Name.Local, a.Value, e, settings.PreserveDuplicateAttrs)
} }
stack.push(e) stack.push(e)
case xml.EndElement: case xml.EndElement:
@@ -1223,17 +1236,20 @@ func (e *Element) addChild(t Token) {
// prefix followed by a colon. // prefix followed by a colon.
func (e *Element) CreateAttr(key, value string) *Attr { func (e *Element) CreateAttr(key, value string) *Attr {
space, skey := spaceDecompose(key) space, skey := spaceDecompose(key)
return e.createAttr(space, skey, value, e) return e.createAttr(space, skey, value, e, false)
} }
// createAttr is a helper function that creates attributes. // createAttr is a helper function that creates attributes.
func (e *Element) createAttr(space, key, value string, parent *Element) *Attr { func (e *Element) createAttr(space, key, value string, parent *Element, preserveDups bool) *Attr {
for i, a := range e.Attr { if !preserveDups {
if space == a.Space && key == a.Key { for i, a := range e.Attr {
e.Attr[i].Value = value if space == a.Space && key == a.Key {
return &e.Attr[i] e.Attr[i].Value = value
return &e.Attr[i]
}
} }
} }
a := Attr{ a := Attr{
Space: space, Space: space,
Key: key, Key: key,
+4 -4
View File
@@ -299,10 +299,10 @@ func indentLF(n int, source string) string {
} }
} }
// nextIndex returns the index of the next occurrence of sep in s, // nextIndex returns the index of the next occurrence of byte ch in s,
// starting from offset. It returns -1 if the sep string is not found. // starting from offset. It returns -1 if the byte is not found.
func nextIndex(s, sep string, offset int) int { func nextIndex(s string, ch byte, offset int) int {
switch i := strings.Index(s[offset:], sep); i { switch i := strings.IndexByte(s[offset:], ch); i {
case -1: case -1:
return -1 return -1
default: default:
+35 -26
View File
@@ -242,12 +242,17 @@ func splitPath(path string) []string {
var pieces []string var pieces []string
start := 0 start := 0
inquote := false inquote := false
var quote byte
for i := 0; i+1 <= len(path); i++ { for i := 0; i+1 <= len(path); i++ {
if path[i] == '\'' { if !inquote {
inquote = !inquote if path[i] == '\'' || path[i] == '"' {
} else if path[i] == '/' && !inquote { inquote, quote = true, path[i]
pieces = append(pieces, path[start:i]) } else if path[i] == '/' {
start = i + 1 pieces = append(pieces, path[start:i])
start = i + 1
}
} else if path[i] == quote {
inquote = false
} }
} }
return append(pieces, path[start:]) return append(pieces, path[start:])
@@ -302,30 +307,34 @@ func (c *compiler) parseFilter(path string) filter {
return nil return nil
} }
// Filter contains [@attr='val'], [fn()='val'], or [tag='val']? // Filter contains [@attr='val'], [@attr="val"], [fn()='val'],
eqindex := strings.Index(path, "='") // [fn()="val"], [tag='val'] or [tag="val"]?
if eqindex >= 0 { eqindex := strings.IndexByte(path, '=')
rindex := nextIndex(path, "'", eqindex+2) if eqindex >= 0 && eqindex+1 < len(path) {
if rindex != len(path)-1 { quote := path[eqindex+1]
c.err = ErrPath("path has mismatched filter quotes.") if quote == '\'' || quote == '"' {
return nil rindex := nextIndex(path, quote, eqindex+2)
} if rindex != len(path)-1 {
c.err = ErrPath("path has mismatched filter quotes.")
return nil
}
key := path[:eqindex] key := path[:eqindex]
value := path[eqindex+2 : rindex] value := path[eqindex+2 : rindex]
switch { switch {
case key[0] == '@': case key[0] == '@':
return newFilterAttrVal(key[1:], value) return newFilterAttrVal(key[1:], value)
case strings.HasSuffix(key, "()"): case strings.HasSuffix(key, "()"):
name := key[:len(key)-2] name := key[:len(key)-2]
if fn, ok := fnTable[name]; ok { if fn, ok := fnTable[name]; ok {
return newFilterFuncVal(fn, value) return newFilterFuncVal(fn, value)
}
c.err = ErrPath("path has unknown function " + name)
return nil
default:
return newFilterChildText(key, value)
} }
c.err = ErrPath("path has unknown function " + name)
return nil
default:
return newFilterChildText(key, value)
} }
} }
-2
View File
@@ -1,2 +0,0 @@
/bin
/gopath
-16
View File
@@ -1,16 +0,0 @@
language: go
go:
- "1.12"
- "1.13"
install:
- go get -v -t github.com/coreos/go-oidc/...
- go get golang.org/x/tools/cmd/cover
- go get golang.org/x/lint/golint
script:
- ./test
notifications:
email: false
-71
View File
@@ -1,71 +0,0 @@
# How to Contribute
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
GitHub pull requests. This document outlines some of the conventions on
development workflow, commit message formatting, contact points and other
resources to make it easier to get your contribution accepted.
# Certificate of Origin
By contributing to this project you agree to the Developer Certificate of
Origin (DCO). This document was created by the Linux Kernel community and is a
simple statement that you, as a contributor, have the legal right to make the
contribution. See the [DCO](DCO) file for details.
# Email and Chat
The project currently uses the general CoreOS email list and IRC channel:
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
are very busy and read the mailing lists.
## Getting Started
- Fork the repository on GitHub
- Read the [README](README.md) for build and test instructions
- Play with the project, submit bugs, submit patches!
## Contribution Flow
This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
Thanks for your contributions!
### Format of the Commit Message
We follow a rough convention for commit messages that is designed to answer two
questions: what changed and why. The subject line should feature the what and
the body of the commit should describe the why.
```
scripts: add the test-cluster command
this uses tmux to setup a test cluster that you can easily kill and
start for debugging.
Fixes #38
```
The format can be described more formally as follows:
```
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.
-36
View File
@@ -1,36 +0,0 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
-202
View File
@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-3
View File
@@ -1,3 +0,0 @@
Eric Chiang <ericchiang@google.com> (@ericchiang)
Mike Danese <mikedanese@google.com> (@mikedanese)
Rithu Leena John <rjohn@redhat.com> (@rithujohn191)
-5
View File
@@ -1,5 +0,0 @@
CoreOS Project
Copyright 2014 CoreOS, Inc
This product includes software developed at CoreOS, Inc.
(http://www.coreos.com/).
-72
View File
@@ -1,72 +0,0 @@
# go-oidc
[![GoDoc](https://godoc.org/github.com/coreos/go-oidc?status.svg)](https://godoc.org/github.com/coreos/go-oidc)
[![Build Status](https://travis-ci.org/coreos/go-oidc.png?branch=master)](https://travis-ci.org/coreos/go-oidc)
## OpenID Connect support for Go
This package enables OpenID Connect support for the [golang.org/x/oauth2](https://godoc.org/golang.org/x/oauth2) package.
```go
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
// handle error
}
// Configure an OpenID Connect aware OAuth2 client.
oauth2Config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
// Discovery returns the OAuth2 endpoints.
Endpoint: provider.Endpoint(),
// "openid" is a required scope for OpenID Connect flows.
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
```
OAuth2 redirects are unchanged.
```go
func handleRedirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
}
```
The on responses, the provider can be used to verify ID Tokens.
```go
var verifier = provider.Verifier(&oidc.Config{ClientID: clientID})
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
// Verify state and errors.
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
// handle error
}
// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
// handle missing token
}
// Parse and verify ID Token payload.
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
// handle error
}
// Extract custom claims
var claims struct {
Email string `json:"email"`
Verified bool `json:"email_verified"`
}
if err := idToken.Claims(&claims); err != nil {
// handle error
}
}
```
-61
View File
@@ -1,61 +0,0 @@
## CoreOS Community Code of Conduct
### Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of
fostering an open and welcoming community, we pledge to respect all people who
contribute through reporting issues, posting feature requests, updating
documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct. By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently applying these
principles to every aspect of managing this project. Project maintainers who do
not follow or enforce the Code of Conduct may be permanently removed from the
project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting a project maintainer, Brandon Philips
<brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
This Code of Conduct is adapted from the Contributor Covenant
(http://contributor-covenant.org), version 1.2.0, available at
http://contributor-covenant.org/version/1/2/0/
### CoreOS Events Code of Conduct
CoreOS events are working conferences intended for professional networking and
collaboration in the CoreOS community. Attendees are expected to behave
according to professional standards and in accordance with their employers
policies on appropriate workplace behavior.
While at CoreOS events or related social networking opportunities, attendees
should not engage in discriminatory or offensive speech or actions including
but not limited to gender, sexuality, race, age, disability, or religion.
Speakers should be especially aware of these concerns.
CoreOS does not condone any statements by speakers contrary to these standards.
CoreOS reserves the right to deny entrance and/or eject from an event (without
refund) any individual found to be engaging in discriminatory or offensive
speech or actions.
Please bring any concerns to the immediate attention of designated on-site
staff, Brandon Philips <brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
-20
View File
@@ -1,20 +0,0 @@
// +build !golint
// Don't lint this file. We don't want to have to add a comment to each constant.
package oidc
const (
// JOSE asymmetric signing algorithm values as defined by RFC 7518
//
// see: https://tools.ietf.org/html/rfc7518#section-3.1
RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
ES256 = "ES256" // ECDSA using P-256 and SHA-256
ES384 = "ES384" // ECDSA using P-384 and SHA-384
ES512 = "ES512" // ECDSA using P-521 and SHA-512
PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
)
-228
View File
@@ -1,228 +0,0 @@
package oidc
import (
"context"
"errors"
"fmt"
"io/ioutil"
"net/http"
"sync"
"time"
"github.com/pquerna/cachecontrol"
jose "gopkg.in/square/go-jose.v2"
)
// keysExpiryDelta is the allowed clock skew between a client and the OpenID Connect
// server.
//
// When keys expire, they are valid for this amount of time after.
//
// If the keys have not expired, and an ID Token claims it was signed by a key not in
// the cache, if and only if the keys expire in this amount of time, the keys will be
// updated.
const keysExpiryDelta = 30 * time.Second
// NewRemoteKeySet returns a KeySet that can validate JSON web tokens by using HTTP
// GETs to fetch JSON web token sets hosted at a remote URL. This is automatically
// used by NewProvider using the URLs returned by OpenID Connect discovery, but is
// exposed for providers that don't support discovery or to prevent round trips to the
// discovery URL.
//
// The returned KeySet is a long lived verifier that caches keys based on cache-control
// headers. Reuse a common remote key set instead of creating new ones as needed.
//
// The behavior of the returned KeySet is undefined once the context is canceled.
func NewRemoteKeySet(ctx context.Context, jwksURL string) KeySet {
return newRemoteKeySet(ctx, jwksURL, time.Now)
}
func newRemoteKeySet(ctx context.Context, jwksURL string, now func() time.Time) *remoteKeySet {
if now == nil {
now = time.Now
}
return &remoteKeySet{jwksURL: jwksURL, ctx: ctx, now: now}
}
type remoteKeySet struct {
jwksURL string
ctx context.Context
now func() time.Time
// guard all other fields
mu sync.Mutex
// inflight suppresses parallel execution of updateKeys and allows
// multiple goroutines to wait for its result.
inflight *inflight
// A set of cached keys and their expiry.
cachedKeys []jose.JSONWebKey
expiry time.Time
}
// inflight is used to wait on some in-flight request from multiple goroutines.
type inflight struct {
doneCh chan struct{}
keys []jose.JSONWebKey
err error
}
func newInflight() *inflight {
return &inflight{doneCh: make(chan struct{})}
}
// wait returns a channel that multiple goroutines can receive on. Once it returns
// a value, the inflight request is done and result() can be inspected.
func (i *inflight) wait() <-chan struct{} {
return i.doneCh
}
// done can only be called by a single goroutine. It records the result of the
// inflight request and signals other goroutines that the result is safe to
// inspect.
func (i *inflight) done(keys []jose.JSONWebKey, err error) {
i.keys = keys
i.err = err
close(i.doneCh)
}
// result cannot be called until the wait() channel has returned a value.
func (i *inflight) result() ([]jose.JSONWebKey, error) {
return i.keys, i.err
}
func (r *remoteKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
jws, err := jose.ParseSigned(jwt)
if err != nil {
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
}
return r.verify(ctx, jws)
}
func (r *remoteKeySet) verify(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
// We don't support JWTs signed with multiple signatures.
keyID := ""
for _, sig := range jws.Signatures {
keyID = sig.Header.KeyID
break
}
keys, expiry := r.keysFromCache()
// Don't check expiry yet. This optimizes for when the provider is unavailable.
for _, key := range keys {
if keyID == "" || key.KeyID == keyID {
if payload, err := jws.Verify(&key); err == nil {
return payload, nil
}
}
}
if !r.now().Add(keysExpiryDelta).After(expiry) {
// Keys haven't expired, don't refresh.
return nil, errors.New("failed to verify id token signature")
}
keys, err := r.keysFromRemote(ctx)
if err != nil {
return nil, fmt.Errorf("fetching keys %v", err)
}
for _, key := range keys {
if keyID == "" || key.KeyID == keyID {
if payload, err := jws.Verify(&key); err == nil {
return payload, nil
}
}
}
return nil, errors.New("failed to verify id token signature")
}
func (r *remoteKeySet) keysFromCache() (keys []jose.JSONWebKey, expiry time.Time) {
r.mu.Lock()
defer r.mu.Unlock()
return r.cachedKeys, r.expiry
}
// keysFromRemote syncs the key set from the remote set, records the values in the
// cache, and returns the key set.
func (r *remoteKeySet) keysFromRemote(ctx context.Context) ([]jose.JSONWebKey, error) {
// Need to lock to inspect the inflight request field.
r.mu.Lock()
// If there's not a current inflight request, create one.
if r.inflight == nil {
r.inflight = newInflight()
// This goroutine has exclusive ownership over the current inflight
// request. It releases the resource by nil'ing the inflight field
// once the goroutine is done.
go func() {
// Sync keys and finish inflight when that's done.
keys, expiry, err := r.updateKeys()
r.inflight.done(keys, err)
// Lock to update the keys and indicate that there is no longer an
// inflight request.
r.mu.Lock()
defer r.mu.Unlock()
if err == nil {
r.cachedKeys = keys
r.expiry = expiry
}
// Free inflight so a different request can run.
r.inflight = nil
}()
}
inflight := r.inflight
r.mu.Unlock()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-inflight.wait():
return inflight.result()
}
}
func (r *remoteKeySet) updateKeys() ([]jose.JSONWebKey, time.Time, error) {
req, err := http.NewRequest("GET", r.jwksURL, nil)
if err != nil {
return nil, time.Time{}, fmt.Errorf("oidc: can't create request: %v", err)
}
resp, err := doRequest(r.ctx, req)
if err != nil {
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, time.Time{}, fmt.Errorf("unable to read response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed: %s %s", resp.Status, body)
}
var keySet jose.JSONWebKeySet
err = unmarshalResp(resp, body, &keySet)
if err != nil {
return nil, time.Time{}, fmt.Errorf("oidc: failed to decode keys: %v %s", err, body)
}
// If the server doesn't provide cache control headers, assume the
// keys expire immediately.
expiry := r.now()
_, e, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{})
if err == nil && e.After(expiry) {
expiry = e
}
return keySet.Keys, expiry, nil
}
-409
View File
@@ -1,409 +0,0 @@
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
package oidc
import (
"context"
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"hash"
"io/ioutil"
"mime"
"net/http"
"strings"
"time"
"golang.org/x/oauth2"
jose "gopkg.in/square/go-jose.v2"
)
const (
// ScopeOpenID is the mandatory scope for all OpenID Connect OAuth2 requests.
ScopeOpenID = "openid"
// ScopeOfflineAccess is an optional scope defined by OpenID Connect for requesting
// OAuth2 refresh tokens.
//
// Support for this scope differs between OpenID Connect providers. For instance
// Google rejects it, favoring appending "access_type=offline" as part of the
// authorization request instead.
//
// See: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
ScopeOfflineAccess = "offline_access"
)
var (
errNoAtHash = errors.New("id token did not have an access token hash")
errInvalidAtHash = errors.New("access token hash does not match value in ID token")
)
// ClientContext returns a new Context that carries the provided HTTP client.
//
// This method sets the same context key used by the golang.org/x/oauth2 package,
// so the returned context works for that package too.
//
// myClient := &http.Client{}
// ctx := oidc.ClientContext(parentContext, myClient)
//
// // This will use the custom client
// provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
//
func ClientContext(ctx context.Context, client *http.Client) context.Context {
return context.WithValue(ctx, oauth2.HTTPClient, client)
}
func doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
client := http.DefaultClient
if c, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
client = c
}
return client.Do(req.WithContext(ctx))
}
// Provider represents an OpenID Connect server's configuration.
type Provider struct {
issuer string
authURL string
tokenURL string
userInfoURL string
algorithms []string
// Raw claims returned by the server.
rawClaims []byte
remoteKeySet KeySet
}
type cachedKeys struct {
keys []jose.JSONWebKey
expiry time.Time
}
type providerJSON struct {
Issuer string `json:"issuer"`
AuthURL string `json:"authorization_endpoint"`
TokenURL string `json:"token_endpoint"`
JWKSURL string `json:"jwks_uri"`
UserInfoURL string `json:"userinfo_endpoint"`
Algorithms []string `json:"id_token_signing_alg_values_supported"`
}
// supportedAlgorithms is a list of algorithms explicitly supported by this
// package. If a provider supports other algorithms, such as HS256 or none,
// those values won't be passed to the IDTokenVerifier.
var supportedAlgorithms = map[string]bool{
RS256: true,
RS384: true,
RS512: true,
ES256: true,
ES384: true,
ES512: true,
PS256: true,
PS384: true,
PS512: true,
}
// NewProvider uses the OpenID Connect discovery mechanism to construct a Provider.
//
// The issuer is the URL identifier for the service. For example: "https://accounts.google.com"
// or "https://login.salesforce.com".
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
req, err := http.NewRequest("GET", wellKnown, nil)
if err != nil {
return nil, err
}
resp, err := doRequest(ctx, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s: %s", resp.Status, body)
}
var p providerJSON
err = unmarshalResp(resp, body, &p)
if err != nil {
return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
}
if p.Issuer != issuer {
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
}
var algs []string
for _, a := range p.Algorithms {
if supportedAlgorithms[a] {
algs = append(algs, a)
}
}
return &Provider{
issuer: p.Issuer,
authURL: p.AuthURL,
tokenURL: p.TokenURL,
userInfoURL: p.UserInfoURL,
algorithms: algs,
rawClaims: body,
remoteKeySet: NewRemoteKeySet(ctx, p.JWKSURL),
}, nil
}
// Claims unmarshals raw fields returned by the server during discovery.
//
// var claims struct {
// ScopesSupported []string `json:"scopes_supported"`
// ClaimsSupported []string `json:"claims_supported"`
// }
//
// if err := provider.Claims(&claims); err != nil {
// // handle unmarshaling error
// }
//
// For a list of fields defined by the OpenID Connect spec see:
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
func (p *Provider) Claims(v interface{}) error {
if p.rawClaims == nil {
return errors.New("oidc: claims not set")
}
return json.Unmarshal(p.rawClaims, v)
}
// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
func (p *Provider) Endpoint() oauth2.Endpoint {
return oauth2.Endpoint{AuthURL: p.authURL, TokenURL: p.tokenURL}
}
// UserInfo represents the OpenID Connect userinfo claims.
type UserInfo struct {
Subject string `json:"sub"`
Profile string `json:"profile"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
claims []byte
}
// Claims unmarshals the raw JSON object claims into the provided object.
func (u *UserInfo) Claims(v interface{}) error {
if u.claims == nil {
return errors.New("oidc: claims not set")
}
return json.Unmarshal(u.claims, v)
}
// UserInfo uses the token source to query the provider's user info endpoint.
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
if p.userInfoURL == "" {
return nil, errors.New("oidc: user info endpoint is not supported by this provider")
}
req, err := http.NewRequest("GET", p.userInfoURL, nil)
if err != nil {
return nil, fmt.Errorf("oidc: create GET request: %v", err)
}
token, err := tokenSource.Token()
if err != nil {
return nil, fmt.Errorf("oidc: get access token: %v", err)
}
token.SetAuthHeader(req)
resp, err := doRequest(ctx, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s: %s", resp.Status, body)
}
var userInfo UserInfo
if err := json.Unmarshal(body, &userInfo); err != nil {
return nil, fmt.Errorf("oidc: failed to decode userinfo: %v", err)
}
userInfo.claims = body
return &userInfo, nil
}
// IDToken is an OpenID Connect extension that provides a predictable representation
// of an authorization event.
//
// The ID Token only holds fields OpenID Connect requires. To access additional
// claims returned by the server, use the Claims method.
type IDToken struct {
// The URL of the server which issued this token. OpenID Connect
// requires this value always be identical to the URL used for
// initial discovery.
//
// Note: Because of a known issue with Google Accounts' implementation
// this value may differ when using Google.
//
// See: https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo
Issuer string
// The client ID, or set of client IDs, that this token is issued for. For
// common uses, this is the client that initialized the auth flow.
//
// This package ensures the audience contains an expected value.
Audience []string
// A unique string which identifies the end user.
Subject string
// Expiry of the token. Ths package will not process tokens that have
// expired unless that validation is explicitly turned off.
Expiry time.Time
// When the token was issued by the provider.
IssuedAt time.Time
// Initial nonce provided during the authentication redirect.
//
// This package does NOT provided verification on the value of this field
// and it's the user's responsibility to ensure it contains a valid value.
Nonce string
// at_hash claim, if set in the ID token. Callers can verify an access token
// that corresponds to the ID token using the VerifyAccessToken method.
AccessTokenHash string
// signature algorithm used for ID token, needed to compute a verification hash of an
// access token
sigAlgorithm string
// Raw payload of the id_token.
claims []byte
// Map of distributed claim names to claim sources
distributedClaims map[string]claimSource
}
// Claims unmarshals the raw JSON payload of the ID Token into a provided struct.
//
// idToken, err := idTokenVerifier.Verify(rawIDToken)
// if err != nil {
// // handle error
// }
// var claims struct {
// Email string `json:"email"`
// EmailVerified bool `json:"email_verified"`
// }
// if err := idToken.Claims(&claims); err != nil {
// // handle error
// }
//
func (i *IDToken) Claims(v interface{}) error {
if i.claims == nil {
return errors.New("oidc: claims not set")
}
return json.Unmarshal(i.claims, v)
}
// VerifyAccessToken verifies that the hash of the access token that corresponds to the iD token
// matches the hash in the id token. It returns an error if the hashes don't match.
// It is the caller's responsibility to ensure that the optional access token hash is present for the ID token
// before calling this method. See https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
func (i *IDToken) VerifyAccessToken(accessToken string) error {
if i.AccessTokenHash == "" {
return errNoAtHash
}
var h hash.Hash
switch i.sigAlgorithm {
case RS256, ES256, PS256:
h = sha256.New()
case RS384, ES384, PS384:
h = sha512.New384()
case RS512, ES512, PS512:
h = sha512.New()
default:
return fmt.Errorf("oidc: unsupported signing algorithm %q", i.sigAlgorithm)
}
h.Write([]byte(accessToken)) // hash documents that Write will never return an error
sum := h.Sum(nil)[:h.Size()/2]
actual := base64.RawURLEncoding.EncodeToString(sum)
if actual != i.AccessTokenHash {
return errInvalidAtHash
}
return nil
}
type idToken struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience audience `json:"aud"`
Expiry jsonTime `json:"exp"`
IssuedAt jsonTime `json:"iat"`
NotBefore *jsonTime `json:"nbf"`
Nonce string `json:"nonce"`
AtHash string `json:"at_hash"`
ClaimNames map[string]string `json:"_claim_names"`
ClaimSources map[string]claimSource `json:"_claim_sources"`
}
type claimSource struct {
Endpoint string `json:"endpoint"`
AccessToken string `json:"access_token"`
}
type audience []string
func (a *audience) UnmarshalJSON(b []byte) error {
var s string
if json.Unmarshal(b, &s) == nil {
*a = audience{s}
return nil
}
var auds []string
if err := json.Unmarshal(b, &auds); err != nil {
return err
}
*a = audience(auds)
return nil
}
type jsonTime time.Time
func (j *jsonTime) UnmarshalJSON(b []byte) error {
var n json.Number
if err := json.Unmarshal(b, &n); err != nil {
return err
}
var unix int64
if t, err := n.Int64(); err == nil {
unix = t
} else {
f, err := n.Float64()
if err != nil {
return err
}
unix = int64(f)
}
*j = jsonTime(time.Unix(unix, 0))
return nil
}
func unmarshalResp(r *http.Response, body []byte, v interface{}) error {
err := json.Unmarshal(body, &v)
if err == nil {
return nil
}
ct := r.Header.Get("Content-Type")
mediaType, _, parseErr := mime.ParseMediaType(ct)
if parseErr == nil && mediaType == "application/json" {
return fmt.Errorf("got Content-Type = application/json, but could not unmarshal as JSON: %v", err)
}
return fmt.Errorf("expected Content-Type = application/json, got %q: %v", ct, err)
}
-16
View File
@@ -1,16 +0,0 @@
#!/bin/bash
set -e
# Filter out any files with a !golint build tag.
LINTABLE=$( go list -tags=golint -f '
{{- range $i, $file := .GoFiles -}}
{{ $file }} {{ end }}
{{ range $i, $file := .TestGoFiles -}}
{{ $file }} {{ end }}' github.com/coreos/go-oidc )
go test -v -i -race github.com/coreos/go-oidc/...
go test -v -race github.com/coreos/go-oidc/...
golint -set_exit_status $LINTABLE
go vet github.com/coreos/go-oidc/...
go build -v ./example/...
-336
View File
@@ -1,336 +0,0 @@
package oidc
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"golang.org/x/oauth2"
jose "gopkg.in/square/go-jose.v2"
)
const (
issuerGoogleAccounts = "https://accounts.google.com"
issuerGoogleAccountsNoScheme = "accounts.google.com"
)
// KeySet is a set of publc JSON Web Keys that can be used to validate the signature
// of JSON web tokens. This is expected to be backed by a remote key set through
// provider metadata discovery or an in-memory set of keys delivered out-of-band.
type KeySet interface {
// VerifySignature parses the JSON web token, verifies the signature, and returns
// the raw payload. Header and claim fields are validated by other parts of the
// package. For example, the KeySet does not need to check values such as signature
// algorithm, issuer, and audience since the IDTokenVerifier validates these values
// independently.
//
// If VerifySignature makes HTTP requests to verify the token, it's expected to
// use any HTTP client associated with the context through ClientContext.
VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)
}
// IDTokenVerifier provides verification for ID Tokens.
type IDTokenVerifier struct {
keySet KeySet
config *Config
issuer string
}
// NewVerifier returns a verifier manually constructed from a key set and issuer URL.
//
// It's easier to use provider discovery to construct an IDTokenVerifier than creating
// one directly. This method is intended to be used with provider that don't support
// metadata discovery, or avoiding round trips when the key set URL is already known.
//
// This constructor can be used to create a verifier directly using the issuer URL and
// JSON Web Key Set URL without using discovery:
//
// keySet := oidc.NewRemoteKeySet(ctx, "https://www.googleapis.com/oauth2/v3/certs")
// verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
//
// Since KeySet is an interface, this constructor can also be used to supply custom
// public key sources. For example, if a user wanted to supply public keys out-of-band
// and hold them statically in-memory:
//
// // Custom KeySet implementation.
// keySet := newStatisKeySet(publicKeys...)
//
// // Verifier uses the custom KeySet implementation.
// verifier := oidc.NewVerifier("https://auth.example.com", keySet, config)
//
func NewVerifier(issuerURL string, keySet KeySet, config *Config) *IDTokenVerifier {
return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL}
}
// Config is the configuration for an IDTokenVerifier.
type Config struct {
// Expected audience of the token. For a majority of the cases this is expected to be
// the ID of the client that initialized the login flow. It may occasionally differ if
// the provider supports the authorizing party (azp) claim.
//
// If not provided, users must explicitly set SkipClientIDCheck.
ClientID string
// If specified, only this set of algorithms may be used to sign the JWT.
//
// If the IDTokenVerifier is created from a provider with (*Provider).Verifier, this
// defaults to the set of algorithms the provider supports. Otherwise this values
// defaults to RS256.
SupportedSigningAlgs []string
// If true, no ClientID check performed. Must be true if ClientID field is empty.
SkipClientIDCheck bool
// If true, token expiry is not checked.
SkipExpiryCheck bool
// SkipIssuerCheck is intended for specialized cases where the the caller wishes to
// defer issuer validation. When enabled, callers MUST independently verify the Token's
// Issuer is a known good value.
//
// Mismatched issuers often indicate client mis-configuration. If mismatches are
// unexpected, evaluate if the provided issuer URL is incorrect instead of enabling
// this option.
SkipIssuerCheck bool
// Time function to check Token expiry. Defaults to time.Now
Now func() time.Time
}
// Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
//
// The returned IDTokenVerifier is tied to the Provider's context and its behavior is
// undefined once the Provider's context is canceled.
func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
if len(config.SupportedSigningAlgs) == 0 && len(p.algorithms) > 0 {
// Make a copy so we don't modify the config values.
cp := &Config{}
*cp = *config
cp.SupportedSigningAlgs = p.algorithms
config = cp
}
return NewVerifier(p.issuer, p.remoteKeySet, config)
}
func parseJWT(p string) ([]byte, error) {
parts := strings.Split(p, ".")
if len(parts) < 2 {
return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
}
return payload, nil
}
func contains(sli []string, ele string) bool {
for _, s := range sli {
if s == ele {
return true
}
}
return false
}
// Returns the Claims from the distributed JWT token
func resolveDistributedClaim(ctx context.Context, verifier *IDTokenVerifier, src claimSource) ([]byte, error) {
req, err := http.NewRequest("GET", src.Endpoint, nil)
if err != nil {
return nil, fmt.Errorf("malformed request: %v", err)
}
if src.AccessToken != "" {
req.Header.Set("Authorization", "Bearer "+src.AccessToken)
}
resp, err := doRequest(ctx, req)
if err != nil {
return nil, fmt.Errorf("oidc: Request to endpoint failed: %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to read response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("oidc: request failed: %v", resp.StatusCode)
}
token, err := verifier.Verify(ctx, string(body))
if err != nil {
return nil, fmt.Errorf("malformed response body: %v", err)
}
return token.claims, nil
}
func parseClaim(raw []byte, name string, v interface{}) error {
var parsed map[string]json.RawMessage
if err := json.Unmarshal(raw, &parsed); err != nil {
return err
}
val, ok := parsed[name]
if !ok {
return fmt.Errorf("claim doesn't exist: %s", name)
}
return json.Unmarshal([]byte(val), v)
}
// Verify parses a raw ID Token, verifies it's been signed by the provider, preforms
// any additional checks depending on the Config, and returns the payload.
//
// Verify does NOT do nonce validation, which is the callers responsibility.
//
// See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
//
// oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
// if err != nil {
// // handle error
// }
//
// // Extract the ID Token from oauth2 token.
// rawIDToken, ok := oauth2Token.Extra("id_token").(string)
// if !ok {
// // handle error
// }
//
// token, err := verifier.Verify(ctx, rawIDToken)
//
func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
jws, err := jose.ParseSigned(rawIDToken)
if err != nil {
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
}
// Throw out tokens with invalid claims before trying to verify the token. This lets
// us do cheap checks before possibly re-syncing keys.
payload, err := parseJWT(rawIDToken)
if err != nil {
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
}
var token idToken
if err := json.Unmarshal(payload, &token); err != nil {
return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
}
distributedClaims := make(map[string]claimSource)
//step through the token to map claim names to claim sources"
for cn, src := range token.ClaimNames {
if src == "" {
return nil, fmt.Errorf("oidc: failed to obtain source from claim name")
}
s, ok := token.ClaimSources[src]
if !ok {
return nil, fmt.Errorf("oidc: source does not exist")
}
distributedClaims[cn] = s
}
t := &IDToken{
Issuer: token.Issuer,
Subject: token.Subject,
Audience: []string(token.Audience),
Expiry: time.Time(token.Expiry),
IssuedAt: time.Time(token.IssuedAt),
Nonce: token.Nonce,
AccessTokenHash: token.AtHash,
claims: payload,
distributedClaims: distributedClaims,
}
// Check issuer.
if !v.config.SkipIssuerCheck && t.Issuer != v.issuer {
// Google sometimes returns "accounts.google.com" as the issuer claim instead of
// the required "https://accounts.google.com". Detect this case and allow it only
// for Google.
//
// We will not add hooks to let other providers go off spec like this.
if !(v.issuer == issuerGoogleAccounts && t.Issuer == issuerGoogleAccountsNoScheme) {
return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.issuer, t.Issuer)
}
}
// If a client ID has been provided, make sure it's part of the audience. SkipClientIDCheck must be true if ClientID is empty.
//
// This check DOES NOT ensure that the ClientID is the party to which the ID Token was issued (i.e. Authorized party).
if !v.config.SkipClientIDCheck {
if v.config.ClientID != "" {
if !contains(t.Audience, v.config.ClientID) {
return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.ClientID, t.Audience)
}
} else {
return nil, fmt.Errorf("oidc: invalid configuration, clientID must be provided or SkipClientIDCheck must be set")
}
}
// If a SkipExpiryCheck is false, make sure token is not expired.
if !v.config.SkipExpiryCheck {
now := time.Now
if v.config.Now != nil {
now = v.config.Now
}
nowTime := now()
if t.Expiry.Before(nowTime) {
return nil, fmt.Errorf("oidc: token is expired (Token Expiry: %v)", t.Expiry)
}
// If nbf claim is provided in token, ensure that it is indeed in the past.
if token.NotBefore != nil {
nbfTime := time.Time(*token.NotBefore)
leeway := 1 * time.Minute
if nowTime.Add(leeway).Before(nbfTime) {
return nil, fmt.Errorf("oidc: current time %v before the nbf (not before) time: %v", nowTime, nbfTime)
}
}
}
switch len(jws.Signatures) {
case 0:
return nil, fmt.Errorf("oidc: id token not signed")
case 1:
default:
return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
}
sig := jws.Signatures[0]
supportedSigAlgs := v.config.SupportedSigningAlgs
if len(supportedSigAlgs) == 0 {
supportedSigAlgs = []string{RS256}
}
if !contains(supportedSigAlgs, sig.Header.Algorithm) {
return nil, fmt.Errorf("oidc: id token signed with unsupported algorithm, expected %q got %q", supportedSigAlgs, sig.Header.Algorithm)
}
t.sigAlgorithm = sig.Header.Algorithm
gotPayload, err := v.keySet.VerifySignature(ctx, rawIDToken)
if err != nil {
return nil, fmt.Errorf("failed to verify signature: %v", err)
}
// Ensure that the payload returned by the square actually matches the payload parsed earlier.
if !bytes.Equal(gotPayload, payload) {
return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
}
return t, nil
}
// Nonce returns an auth code option which requires the ID Token created by the
// OpenID Connect provider to contain the specified nonce.
func Nonce(nonce string) oauth2.AuthCodeOption {
return oauth2.SetAuthURLParam("nonce", nonce)
}
@@ -22,15 +22,12 @@ import (
"context" "context"
"fmt" "fmt"
"go-micro.dev/v4/util/log"
"google.golang.org/grpc"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
v1beta12 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/appctx"
revactx "github.com/cs3org/reva/v2/pkg/ctx" revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/events" "github.com/cs3org/reva/v2/pkg/events"
"github.com/cs3org/reva/v2/pkg/events/stream" "github.com/cs3org/reva/v2/pkg/events/stream"
@@ -38,6 +35,7 @@ import (
"github.com/cs3org/reva/v2/pkg/storagespace" "github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils" "github.com/cs3org/reva/v2/pkg/utils"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"google.golang.org/grpc"
) )
const ( const (
@@ -190,7 +188,7 @@ func NewUnary(m map[string]interface{}) (grpc.UnaryServerInterceptor, int, error
if ev != nil { if ev != nil {
if err := events.Publish(ctx, publisher, ev); err != nil { if err := events.Publish(ctx, publisher, ev); err != nil {
log.Error(err) appctx.GetLogger(ctx).Error().Err(err).Interface("event", ev).Msg("publishing event failed")
} }
} }
@@ -211,7 +209,7 @@ func NewStream() grpc.StreamServerInterceptor {
// common interface to all responses // common interface to all responses
type su interface { type su interface {
GetStatus() *v1beta12.Status GetStatus() *rpc.Status
} }
func isSuccess(res su) bool { func isSuccess(res su) bool {
@@ -55,6 +55,7 @@ type config struct {
Drivers map[string]map[string]interface{} `mapstructure:"drivers"` Drivers map[string]map[string]interface{} `mapstructure:"drivers"`
GatewayAddr string `mapstructure:"gateway_addr"` GatewayAddr string `mapstructure:"gateway_addr"`
AllowedPathsForShares []string `mapstructure:"allowed_paths_for_shares"` AllowedPathsForShares []string `mapstructure:"allowed_paths_for_shares"`
DisableResharing bool `mapstructure:"disable_resharing"`
} }
func (c *config) init() { func (c *config) init() {
@@ -67,6 +68,7 @@ type service struct {
sm share.Manager sm share.Manager
gatewaySelector pool.Selectable[gateway.GatewayAPIClient] gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
allowedPathsForShares []*regexp.Regexp allowedPathsForShares []*regexp.Regexp
disableResharing bool
} }
func getShareManager(c *config) (share.Manager, error) { func getShareManager(c *config) (share.Manager, error) {
@@ -127,15 +129,16 @@ func NewDefault(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error
return nil, err return nil, err
} }
return New(gatewaySelector, sm, allowedPathsForShares), nil return New(gatewaySelector, sm, allowedPathsForShares, c.DisableResharing), nil
} }
// New creates a new user share provider svc // New creates a new user share provider svc
func New(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], sm share.Manager, allowedPathsForShares []*regexp.Regexp) rgrpc.Service { func New(gatewaySelector pool.Selectable[gateway.GatewayAPIClient], sm share.Manager, allowedPathsForShares []*regexp.Regexp, disableResharing bool) rgrpc.Service {
service := &service{ service := &service{
sm: sm, sm: sm,
gatewaySelector: gatewaySelector, gatewaySelector: gatewaySelector,
allowedPathsForShares: allowedPathsForShares, allowedPathsForShares: allowedPathsForShares,
disableResharing: disableResharing,
} }
return service return service
@@ -157,6 +160,13 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar
log := appctx.GetLogger(ctx) log := appctx.GetLogger(ctx)
user := ctxpkg.ContextMustGetUser(ctx) user := ctxpkg.ContextMustGetUser(ctx)
// when resharing is disabled grants must not allow grant permissions
if s.disableResharing && HasGrantPermissions(req.GetGrant().GetPermissions().GetPermissions()) {
return &collaboration.CreateShareResponse{
Status: status.NewInvalidArg(ctx, "resharing not supported"),
}, nil
}
gatewayClient, err := s.gatewaySelector.Next() gatewayClient, err := s.gatewaySelector.Next()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -235,6 +245,10 @@ func (s *service) CreateShare(ctx context.Context, req *collaboration.CreateShar
}, nil }, nil
} }
func HasGrantPermissions(p *provider.ResourcePermissions) bool {
return p.GetAddGrant() || p.GetUpdateGrant() || p.GetRemoveGrant() || p.GetDenyGrant()
}
func (s *service) RemoveShare(ctx context.Context, req *collaboration.RemoveShareRequest) (*collaboration.RemoveShareResponse, error) { func (s *service) RemoveShare(ctx context.Context, req *collaboration.RemoveShareRequest) (*collaboration.RemoveShareResponse, error) {
log := appctx.GetLogger(ctx) log := appctx.GetLogger(ctx)
user := ctxpkg.ContextMustGetUser(ctx) user := ctxpkg.ContextMustGetUser(ctx)
@@ -327,6 +341,14 @@ func (s *service) ListShares(ctx context.Context, req *collaboration.ListSharesR
func (s *service) UpdateShare(ctx context.Context, req *collaboration.UpdateShareRequest) (*collaboration.UpdateShareResponse, error) { func (s *service) UpdateShare(ctx context.Context, req *collaboration.UpdateShareRequest) (*collaboration.UpdateShareResponse, error) {
log := appctx.GetLogger(ctx) log := appctx.GetLogger(ctx)
user := ctxpkg.ContextMustGetUser(ctx) user := ctxpkg.ContextMustGetUser(ctx)
// when resharing is disabled grants must not allow grant permissions
if s.disableResharing && HasGrantPermissions(req.GetShare().GetPermissions().GetPermissions()) {
return &collaboration.UpdateShareResponse{
Status: status.NewInvalidArg(ctx, "resharing not supported"),
}, nil
}
gatewayClient, err := s.gatewaySelector.Next() gatewayClient, err := s.gatewaySelector.Next()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -50,8 +50,6 @@ func init() {
const ( const (
// TokenTransportHeader holds the header key for the reva transfer token // TokenTransportHeader holds the header key for the reva transfer token
TokenTransportHeader = "X-Reva-Transfer" TokenTransportHeader = "X-Reva-Transfer"
// UploadExpiresHeader holds the timestamp for the transport token expiry, defined in https://tus.io/protocols/resumable-upload.html#expiration
UploadExpiresHeader = "Upload-Expires"
) )
func init() { func init() {
@@ -133,36 +131,13 @@ func (s *svc) setHandler() {
semconv.HTTPURLKey.String(r.URL.String()), semconv.HTTPURLKey.String(r.URL.String()),
) )
r = r.WithContext(ctx) r = r.WithContext(ctx)
switch r.Method { s.doRequest(w, r)
case "HEAD":
addCorsHeader(w)
s.doHead(w, r)
return
case "GET":
s.doGet(w, r)
return
case "PUT":
s.doPut(w, r)
return
case "PATCH":
s.doPatch(w, r)
return
default:
w.WriteHeader(http.StatusNotImplemented)
return
}
}) })
} }
func addCorsHeader(res http.ResponseWriter) { // verify extracts the transfer token from the request
headers := res.Header() // If it is not set as header we assume that it's the last path segment instead.
headers.Set("Access-Control-Allow-Origin", "*")
headers.Set("Access-Control-Allow-Headers", "Content-Type, Origin, Authorization")
headers.Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD")
}
func (s *svc) verify(ctx context.Context, r *http.Request) (*transferClaims, error) { func (s *svc) verify(ctx context.Context, r *http.Request) (*transferClaims, error) {
// Extract transfer token from request header. If not existing, assume that it's the last path segment instead.
token := r.Header.Get(TokenTransportHeader) token := r.Header.Get(TokenTransportHeader)
if token == "" { if token == "" {
token = path.Base(r.URL.Path) token = path.Base(r.URL.Path)
@@ -185,112 +160,7 @@ func (s *svc) verify(ctx context.Context, r *http.Request) (*transferClaims, err
return nil, err return nil, err
} }
func (s *svc) doHead(w http.ResponseWriter, r *http.Request) { func (s *svc) doRequest(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
claims, err := s.verify(ctx, r)
if err != nil {
err = errors.Wrap(err, "datagateway: error validating transfer token")
log.Error().Err(err).Str("token", r.Header.Get(TokenTransportHeader)).Msg("invalid transfer token")
w.WriteHeader(http.StatusForbidden)
return
}
log.Debug().Str("target", claims.Target).Msg("sending request to internal data server")
httpClient := s.client
httpReq, err := rhttp.NewRequest(ctx, "HEAD", claims.Target, nil)
if err != nil {
log.Error().Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError)
return
}
httpReq.Header = r.Header
httpRes, err := httpClient.Do(httpReq)
if err != nil {
log.Error().Err(err).Msg("error doing HEAD request to data service")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
copyHeader(w.Header(), httpRes.Header)
// add upload expiry / transfer token expiry header for tus https://tus.io/protocols/resumable-upload.html#expiration
w.Header().Set(UploadExpiresHeader, time.Unix(claims.ExpiresAt, 0).Format(time.RFC1123))
if httpRes.StatusCode != http.StatusOK {
// swallow the body and set content-length to 0 to prevent reverse proxies from trying to read from it
w.Header().Set("Content-Length", "0")
w.WriteHeader(httpRes.StatusCode)
return
}
w.WriteHeader(http.StatusOK)
}
func (s *svc) doGet(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
claims, err := s.verify(ctx, r)
if err != nil {
err = errors.Wrap(err, "datagateway: error validating transfer token")
log.Error().Err(err).Str("token", r.Header.Get(TokenTransportHeader)).Msg("invalid transfer token")
w.WriteHeader(http.StatusForbidden)
return
}
log.Debug().Str("target", claims.Target).Msg("sending request to internal data server")
httpClient := s.client
httpReq, err := rhttp.NewRequest(ctx, "GET", claims.Target, nil)
if err != nil {
log.Error().Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError)
return
}
httpReq.Header = r.Header
httpRes, err := httpClient.Do(httpReq)
if err != nil {
log.Error().Err(err).Msg("error doing GET request to data service")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
copyHeader(w.Header(), httpRes.Header)
switch httpRes.StatusCode {
case http.StatusOK:
case http.StatusPartialContent:
default:
// swallow the body and set content-length to 0 to prevent reverse proxies from trying to read from it
w.Header().Set("Content-Length", "0")
w.WriteHeader(httpRes.StatusCode)
return
}
w.WriteHeader(httpRes.StatusCode)
var c int64
c, err = io.Copy(w, httpRes.Body)
if err != nil {
log.Error().Err(err).Msg("error writing body after headers were sent")
}
if httpRes.Header.Get("Content-Length") != "" {
i, err := strconv.ParseInt(httpRes.Header.Get("Content-Length"), 10, 64)
if err != nil {
log.Error().Err(err).Str("content-length", httpRes.Header.Get("Content-Length")).Msg("invalid content length in dataprovider response")
}
if i != c {
log.Error().Int64("content-length", i).Int64("transferred-bytes", c).Msg("content length vs transferred bytes mismatch")
}
}
}
func (s *svc) doPut(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
log := appctx.GetLogger(ctx) log := appctx.GetLogger(ctx)
@@ -314,10 +184,9 @@ func (s *svc) doPut(w http.ResponseWriter, r *http.Request) {
targetURL.RawQuery = r.URL.RawQuery targetURL.RawQuery = r.URL.RawQuery
target = targetURL.String() target = targetURL.String()
log.Debug().Str("target", claims.Target).Msg("sending request to internal data server") log.Debug().Str("target", target).Msg("sending request to internal data server")
httpClient := s.client httpReq, err := rhttp.NewRequest(ctx, r.Method, target, r.Body)
httpReq, err := rhttp.NewRequest(ctx, "PUT", target, r.Body)
if err != nil { if err != nil {
log.Err(err).Msg("wrong request") log.Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@@ -326,68 +195,9 @@ func (s *svc) doPut(w http.ResponseWriter, r *http.Request) {
httpReq.Header = r.Header httpReq.Header = r.Header
httpReq.ContentLength = r.ContentLength httpReq.ContentLength = r.ContentLength
httpRes, err := httpClient.Do(httpReq) httpRes, err := s.client.Do(httpReq)
if err != nil { if err != nil {
log.Err(err).Msg("error doing PUT request to data service") log.Err(err).Msg("error doing " + r.Method + " request to data service")
w.WriteHeader(http.StatusInternalServerError)
return
}
defer httpRes.Body.Close()
copyHeader(w.Header(), httpRes.Header)
if httpRes.StatusCode != http.StatusOK {
// swallow the body and set content-length to 0 to prevent reverse proxies from trying to read from it
w.Header().Set("Content-Length", "0")
w.WriteHeader(httpRes.StatusCode)
return
}
w.WriteHeader(http.StatusOK)
_, err = io.Copy(w, httpRes.Body)
if err != nil {
log.Err(err).Msg("error writing body after header were set")
}
}
// TODO: put and post code is pretty much the same. Should be solved in a nicer way in the long run.
func (s *svc) doPatch(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := appctx.GetLogger(ctx)
claims, err := s.verify(ctx, r)
if err != nil {
err = errors.Wrap(err, "datagateway: error validating transfer token")
log.Err(err).Str("token", r.Header.Get(TokenTransportHeader)).Msg("invalid transfer token")
w.WriteHeader(http.StatusForbidden)
return
}
target := claims.Target
// add query params to target, clients can send checksums and other information.
targetURL, err := url.Parse(target)
if err != nil {
log.Err(err).Msg("datagateway: error parsing target url")
w.WriteHeader(http.StatusInternalServerError)
return
}
targetURL.RawQuery = r.URL.RawQuery
target = targetURL.String()
log.Debug().Str("target", claims.Target).Msg("sending request to internal data server")
httpClient := s.client
httpReq, err := rhttp.NewRequest(ctx, "PATCH", target, r.Body)
if err != nil {
log.Err(err).Msg("wrong request")
w.WriteHeader(http.StatusInternalServerError)
return
}
httpReq.Header = r.Header
httpRes, err := httpClient.Do(httpReq)
if err != nil {
log.Err(err).Msg("error doing PATCH request to data service")
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
@@ -400,12 +210,22 @@ func (s *svc) doPatch(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(httpRes.StatusCode) w.WriteHeader(httpRes.StatusCode)
return return
} }
w.WriteHeader(httpRes.StatusCode) w.WriteHeader(httpRes.StatusCode)
_, err = io.Copy(w, httpRes.Body)
var c int64
c, err = io.Copy(w, httpRes.Body)
if err != nil { if err != nil {
log.Err(err).Msg("error writing body after header were set") log.Err(err).Msg("error writing body after header were set")
} }
if httpRes.Header.Get("Content-Length") != "" {
i, err := strconv.ParseInt(httpRes.Header.Get("Content-Length"), 10, 64)
if err != nil {
log.Error().Err(err).Str("content-length", httpRes.Header.Get("Content-Length")).Msg("invalid content length in dataprovider response")
}
if i != c {
log.Error().Int64("content-length", i).Int64("transferred-bytes", c).Msg("content length vs transferred bytes mismatch")
}
}
} }
func copyHeader(dst, src http.Header) { func copyHeader(dst, src http.Header) {
@@ -693,7 +693,7 @@ func (h *Handler) GetShare(w http.ResponseWriter, r *http.Request) {
if share == nil { if share == nil {
sublog.Debug().Msg("no share found with this id") sublog.Debug().Msg("no share found with this id")
response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "share not found", nil) response.WriteOCSError(w, r, response.MetaPathNotFound.StatusCode, "share not found", nil) // MetaNotFount with code 998 would be cleaner, but this is a legacy api
return return
} }
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"strings" "strings"
"time" "time"
oidc "github.com/coreos/go-oidc" oidc "github.com/coreos/go-oidc/v3/oidc"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
+27 -7
View File
@@ -30,6 +30,7 @@ import (
"github.com/rs/zerolog" "github.com/rs/zerolog"
"go-micro.dev/v4/broker" "go-micro.dev/v4/broker"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"google.golang.org/grpc/credentials"
) )
// Option defines a single option function. // Option defines a single option function.
@@ -50,11 +51,10 @@ type Options struct {
FavoriteManager favorite.Manager FavoriteManager favorite.Manager
GatewaySelector pool.Selectable[gateway.GatewayAPIClient] GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
TracingEnabled bool TracingEnabled bool
TracingInsecure bool TracingInsecure bool
TracingExporter string TracingEndpoint string
TracingCollector string TracingTransportCredentials credentials.TransportCredentials
TracingEndpoint string
TraceProvider trace.TracerProvider TraceProvider trace.TracerProvider
@@ -230,11 +230,25 @@ func LockSystem(val ocdav.LockSystem) Option {
} }
// Tracing enables tracing // Tracing enables tracing
// Deprecated: use WithTracingEndpoint and WithTracingEnabled, Collector is unused
func Tracing(endpoint, collector string) Option { func Tracing(endpoint, collector string) Option {
return func(o *Options) { return func(o *Options) {
o.TracingEnabled = true o.TracingEnabled = true
o.TracingEndpoint = endpoint o.TracingEndpoint = endpoint
o.TracingCollector = collector }
}
// WithTracingEnabled option
func WithTracingEnabled(enabled bool) Option {
return func(o *Options) {
o.TracingEnabled = enabled
}
}
// WithTracingEndpoint option
func WithTracingEndpoint(endpoint string) Option {
return func(o *Options) {
o.TracingEndpoint = endpoint
} }
} }
@@ -246,9 +260,15 @@ func WithTracingInsecure() Option {
} }
// WithTracingExporter option // WithTracingExporter option
// Deprecated: unused
func WithTracingExporter(exporter string) Option { func WithTracingExporter(exporter string) Option {
return func(o *Options) {}
}
// WithTracingTransportCredentials option
func WithTracingTransportCredentials(v credentials.TransportCredentials) Option {
return func(o *Options) { return func(o *Options) {
o.TracingExporter = exporter o.TracingTransportCredentials = v
} }
} }
+3 -2
View File
@@ -90,9 +90,7 @@ func Service(opts ...Option) (micro.Service, error) {
if tp == nil { if tp == nil {
topts := []rtrace.Option{ topts := []rtrace.Option{
rtrace.WithExporter(sopts.TracingExporter),
rtrace.WithEndpoint(sopts.TracingEndpoint), rtrace.WithEndpoint(sopts.TracingEndpoint),
rtrace.WithCollector(sopts.TracingCollector),
rtrace.WithServiceName(sopts.Name), rtrace.WithServiceName(sopts.Name),
} }
if sopts.TracingEnabled { if sopts.TracingEnabled {
@@ -101,6 +99,9 @@ func Service(opts ...Option) (micro.Service, error) {
if sopts.TracingInsecure { if sopts.TracingInsecure {
topts = append(topts, rtrace.WithInsecure()) topts = append(topts, rtrace.WithInsecure())
} }
if sopts.TracingTransportCredentials != nil {
topts = append(topts, rtrace.WithTransportCredentials(sopts.TracingTransportCredentials))
}
tp = rtrace.NewTracerProvider(topts...) tp = rtrace.NewTracerProvider(topts...)
} }
if err := useMiddlewares(r, &sopts, revaService, tp); err != nil { if err := useMiddlewares(r, &sopts, revaService, tp); err != nil {
+3 -8
View File
@@ -326,10 +326,6 @@ func (s *Server) getInterceptors(unprotected []string) ([]grpc.ServerOption, err
} }
unaryInterceptors := []grpc.UnaryServerInterceptor{ unaryInterceptors := []grpc.UnaryServerInterceptor{
otelgrpc.UnaryServerInterceptor(
otelgrpc.WithTracerProvider(s.tracerProvider),
otelgrpc.WithPropagators(rtrace.Propagator),
),
appctx.NewUnary(s.log, s.tracerProvider), appctx.NewUnary(s.log, s.tracerProvider),
token.NewUnary(), token.NewUnary(),
useragent.NewUnary(), useragent.NewUnary(),
@@ -372,10 +368,6 @@ func (s *Server) getInterceptors(unprotected []string) ([]grpc.ServerOption, err
} }
streamInterceptors := []grpc.StreamServerInterceptor{ streamInterceptors := []grpc.StreamServerInterceptor{
otelgrpc.StreamServerInterceptor(
otelgrpc.WithTracerProvider(s.tracerProvider),
otelgrpc.WithPropagators(rtrace.Propagator),
),
appctx.NewStream(s.log, s.tracerProvider), appctx.NewStream(s.log, s.tracerProvider),
token.NewStream(), token.NewStream(),
useragent.NewStream(), useragent.NewStream(),
@@ -391,6 +383,9 @@ func (s *Server) getInterceptors(unprotected []string) ([]grpc.ServerOption, err
streamChain := grpc_middleware.ChainStreamServer(streamInterceptors...) streamChain := grpc_middleware.ChainStreamServer(streamInterceptors...)
opts := []grpc.ServerOption{ opts := []grpc.ServerOption{
grpc.StatsHandler(otelgrpc.NewServerHandler(
otelgrpc.WithTracerProvider(s.tracerProvider),
otelgrpc.WithPropagators(rtrace.Propagator))),
grpc.UnaryInterceptor(unaryChain), grpc.UnaryInterceptor(unaryChain),
grpc.StreamInterceptor(streamChain), grpc.StreamInterceptor(streamChain),
} }
+1 -11
View File
@@ -90,7 +90,7 @@ func NewConn(address string, opts ...Option) (*grpc.ClientConn, error) {
grpc.WithDefaultCallOptions( grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(maxRcvMsgSize), grpc.MaxCallRecvMsgSize(maxRcvMsgSize),
), ),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor( grpc.WithStatsHandler(otelgrpc.NewClientHandler(
otelgrpc.WithTracerProvider( otelgrpc.WithTracerProvider(
options.tracerProvider, options.tracerProvider,
), ),
@@ -98,16 +98,6 @@ func NewConn(address string, opts ...Option) (*grpc.ClientConn, error) {
rtrace.Propagator, rtrace.Propagator,
), ),
)), )),
grpc.WithUnaryInterceptor(
otelgrpc.UnaryClientInterceptor(
otelgrpc.WithTracerProvider(
options.tracerProvider,
),
otelgrpc.WithPropagators(
rtrace.Propagator,
),
),
),
) )
if err != nil { if err != nil {
return nil, err return nil, err
+33 -4
View File
@@ -23,6 +23,7 @@ import (
"log" "log"
"net/http" "net/http"
"path" "path"
"regexp"
"github.com/pkg/errors" "github.com/pkg/errors"
tusd "github.com/tus/tusd/pkg/handler" tusd "github.com/tus/tusd/pkg/handler"
@@ -45,14 +46,25 @@ func init() {
registry.Register("tus", New) registry.Register("tus", New)
} }
type TusConfig struct {
cache.Config
CorsEnabled bool `mapstructure:"cors_enabled"`
CorsAllowOrigin string `mapstructure:"cors_allow_origin"`
CorsAllowCredentials bool `mapstructure:"cors_allow_credentials"`
CorsAllowMethods string `mapstructure:"cors_allow_methods"`
CorsAllowHeaders string `mapstructure:"cors_allow_headers"`
CorsMaxAge string `mapstructure:"cors_max_age"`
CorsExposeHeaders string `mapstructure:"cors_expose_headers"`
}
type manager struct { type manager struct {
conf *cache.Config conf *TusConfig
publisher events.Publisher publisher events.Publisher
statCache cache.StatCache statCache cache.StatCache
} }
func parseConfig(m map[string]interface{}) (*cache.Config, error) { func parseConfig(m map[string]interface{}) (*TusConfig, error) {
c := &cache.Config{} c := &TusConfig{}
if err := mapstructure.Decode(m, c); err != nil { if err := mapstructure.Decode(m, c); err != nil {
err = errors.Wrap(err, "error decoding conf") err = errors.Wrap(err, "error decoding conf")
return nil, err return nil, err
@@ -69,7 +81,7 @@ func New(m map[string]interface{}, publisher events.Publisher) (datatx.DataTX, e
return &manager{ return &manager{
conf: c, conf: c,
publisher: publisher, publisher: publisher,
statCache: cache.GetStatCache(*c), statCache: cache.GetStatCache(c.Config),
}, nil }, nil
} }
@@ -94,6 +106,23 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) {
Logger: log.New(appctx.GetLogger(context.Background()), "", 0), Logger: log.New(appctx.GetLogger(context.Background()), "", 0),
} }
if m.conf.CorsEnabled {
allowOrigin, err := regexp.Compile(m.conf.CorsAllowOrigin)
if m.conf.CorsAllowOrigin != "" && err != nil {
return nil, err
}
config.Cors = &tusd.CorsConfig{
Disable: false,
AllowOrigin: allowOrigin,
AllowCredentials: m.conf.CorsAllowCredentials,
AllowMethods: m.conf.CorsAllowMethods,
AllowHeaders: m.conf.CorsAllowHeaders,
MaxAge: m.conf.CorsMaxAge,
ExposeHeaders: m.conf.CorsExposeHeaders,
}
}
handler, err := tusd.NewUnroutedHandler(config) handler, err := tusd.NewUnroutedHandler(config)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -202,6 +202,11 @@ func (c *Cache) Add(ctx context.Context, storageID, spaceID, shareID string, sha
log.Debug().Msg("precondition failed when persisting added provider share: etag changed. retrying...") log.Debug().Msg("precondition failed when persisting added provider share: etag changed. retrying...")
// actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side // actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side
// continue with sync below // continue with sync below
case errtypes.AlreadyExists:
log.Debug().Msg("already exists when persisting added provider share. retrying...")
// CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call.
// Thas happens when the cache thinks there is no file.
// continue with sync below
default: default:
span.SetStatus(codes.Error, fmt.Sprintf("persisting added provider share failed. giving up: %s", err.Error())) span.SetStatus(codes.Error, fmt.Sprintf("persisting added provider share failed. giving up: %s", err.Error()))
log.Error().Err(err).Msg("persisting added provider share failed") log.Error().Err(err).Msg("persisting added provider share failed")
@@ -393,10 +398,8 @@ func (c *Cache) Persist(ctx context.Context, storageID, spaceID string) error {
// > If the field value is "*", the condition is false if the origin server has a current representation for the target resource. // > If the field value is "*", the condition is false if the origin server has a current representation for the target resource.
if space.Etag == "" { if space.Etag == "" {
ur.IfNoneMatch = []string{"*"} ur.IfNoneMatch = []string{"*"}
log.Debug().Msg("setting IfNoneMatch to *")
} else {
log.Debug().Msg("setting IfMatchEtag")
} }
res, err := c.storage.Upload(ctx, ur) res, err := c.storage.Upload(ctx, ur)
if err != nil { if err != nil {
span.RecordError(err) span.RecordError(err)
@@ -405,6 +408,7 @@ func (c *Cache) Persist(ctx context.Context, storageID, spaceID string) error {
return err return err
} }
space.Etag = res.Etag space.Etag = res.Etag
span.SetStatus(codes.Ok, "") span.SetStatus(codes.Ok, "")
shares := []string{} shares := []string{}
for _, s := range space.Shares { for _, s := range space.Shares {
@@ -146,6 +146,11 @@ func (c *Cache) Add(ctx context.Context, userID, spaceID string, rs *collaborati
log.Debug().Msg("precondition failed when persisting added received share: etag changed. retrying...") log.Debug().Msg("precondition failed when persisting added received share: etag changed. retrying...")
// actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side // actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side
// continue with sync below // continue with sync below
case errtypes.AlreadyExists:
log.Debug().Msg("already exists when persisting added received share. retrying...")
// CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call.
// Thas happens when the cache thinks there is no file.
// continue with sync below
default: default:
span.SetStatus(codes.Error, fmt.Sprintf("persisting added received share failed. giving up: %s", err.Error())) span.SetStatus(codes.Error, fmt.Sprintf("persisting added received share failed. giving up: %s", err.Error()))
log.Error().Err(err).Msg("persisting added received share failed") log.Error().Err(err).Msg("persisting added received share failed")
@@ -294,12 +299,14 @@ func (c *Cache) persist(ctx context.Context, userID string) error {
ur.IfNoneMatch = []string{"*"} ur.IfNoneMatch = []string{"*"}
} }
_, err = c.storage.Upload(ctx, ur) res, err := c.storage.Upload(ctx, ur)
if err != nil { if err != nil {
span.RecordError(err) span.RecordError(err)
span.SetStatus(codes.Error, err.Error()) span.SetStatus(codes.Error, err.Error())
return err return err
} }
rss.etag = res.Etag
span.SetStatus(codes.Ok, "") span.SetStatus(codes.Ok, "")
return nil return nil
} }
@@ -137,6 +137,11 @@ func (c *Cache) Add(ctx context.Context, userid, shareID string) error {
log.Debug().Msg("precondition failed when persisting added share: etag changed. retrying...") log.Debug().Msg("precondition failed when persisting added share: etag changed. retrying...")
// actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side // actually, this is the wrong status code and we treat it like errtypes.Aborted because of inconsistencies on the server side
// continue with sync below // continue with sync below
case errtypes.AlreadyExists:
log.Debug().Msg("already exists when persisting added share. retrying...")
// CS3 uses an already exists error instead of precondition failed when using an If-None-Match=* header / IfExists flag in the InitiateFileUpload call.
// Thas happens when the cache thinks there is no file.
// continue with sync below
default: default:
span.SetStatus(codes.Error, fmt.Sprintf("persisting added share failed. giving up: %s", err.Error())) span.SetStatus(codes.Error, fmt.Sprintf("persisting added share failed. giving up: %s", err.Error()))
log.Error().Err(err).Msg("persisting added share failed") log.Error().Err(err).Msg("persisting added share failed")
@@ -330,6 +335,7 @@ func (c *Cache) Persist(ctx context.Context, userid string) error {
if us.Etag == "" { if us.Etag == "" {
ur.IfNoneMatch = []string{"*"} ur.IfNoneMatch = []string{"*"}
} }
res, err := c.storage.Upload(ctx, ur) res, err := c.storage.Upload(ctx, ur)
if err != nil { if err != nil {
span.RecordError(err) span.RecordError(err)
@@ -337,6 +343,7 @@ func (c *Cache) Persist(ctx context.Context, userid string) error {
return err return err
} }
us.Etag = res.Etag us.Etag = res.Etag
span.SetStatus(codes.Ok, "") span.SetStatus(codes.Ok, "")
return nil return nil
} }
@@ -37,11 +37,22 @@ import (
type Blobstore struct { type Blobstore struct {
client *minio.Client client *minio.Client
defaultPutOptions Options
bucket string bucket string
} }
type Options struct {
DisableContentSha256 bool
DisableMultipart bool
SendContentMd5 bool
ConcurrentStreamParts bool
NumThreads uint
PartSize uint64
}
// New returns a new Blobstore // New returns a new Blobstore
func New(endpoint, region, bucket, accessKey, secretKey string) (*Blobstore, error) { func New(endpoint, region, bucket, accessKey, secretKey string, defaultPutOptions Options) (*Blobstore, error) {
u, err := url.Parse(endpoint) u, err := url.Parse(endpoint)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to parse s3 endpoint") return nil, errors.Wrap(err, "failed to parse s3 endpoint")
@@ -58,8 +69,9 @@ func New(endpoint, region, bucket, accessKey, secretKey string) (*Blobstore, err
} }
return &Blobstore{ return &Blobstore{
client: client, client: client,
bucket: bucket, bucket: bucket,
defaultPutOptions: defaultPutOptions,
}, nil }, nil
} }
@@ -71,7 +83,15 @@ func (bs *Blobstore) Upload(node *node.Node, source string) error {
} }
defer reader.Close() defer reader.Close()
_, err = bs.client.PutObject(context.Background(), bs.bucket, bs.path(node), reader, node.Blobsize, minio.PutObjectOptions{ContentType: "application/octet-stream", SendContentMd5: true}) _, err = bs.client.PutObject(context.Background(), bs.bucket, bs.path(node), reader, node.Blobsize, minio.PutObjectOptions{
ContentType: "application/octet-stream",
SendContentMd5: bs.defaultPutOptions.SendContentMd5,
ConcurrentStreamParts: bs.defaultPutOptions.ConcurrentStreamParts,
NumThreads: bs.defaultPutOptions.NumThreads,
PartSize: bs.defaultPutOptions.PartSize,
DisableMultipart: bs.defaultPutOptions.DisableMultipart,
DisableContentSha256: bs.defaultPutOptions.DisableContentSha256,
})
if err != nil { if err != nil {
return errors.Wrapf(err, "could not store object '%s' into bucket '%s'", bs.path(node), bs.bucket) return errors.Wrapf(err, "could not store object '%s' into bucket '%s'", bs.path(node), bs.bucket)
+29
View File
@@ -43,6 +43,24 @@ type Options struct {
// Secret key for the s3 blobstore // Secret key for the s3 blobstore
S3SecretKey string `mapstructure:"s3.secret_key"` S3SecretKey string `mapstructure:"s3.secret_key"`
// disable sending content sha256
DisableContentSha256 bool `mapstructure:"s3.disable_content_sha254"`
// disable multipart uploads
DisableMultipart bool `mapstructure:"s3.disable_multipart"`
// enable sending content md5, defaults to true if unset
SendContentMd5 bool `mapstructure:"s3.send_content_md5"`
// use concurrent stream parts
ConcurrentStreamParts bool `mapstructure:"s3.concurrent_stream_parts"`
// number of concurrent uploads
NumThreads uint `mapstructure:"s3.num_threads"`
// part size for concurrent uploads
PartSize uint64 `mapstructure:"s3.part_size"`
} }
// S3ConfigComplete return true if all required s3 fields are set // S3ConfigComplete return true if all required s3 fields are set
@@ -60,5 +78,16 @@ func parseConfig(m map[string]interface{}) (*Options, error) {
err = errors.Wrap(err, "error decoding conf") err = errors.Wrap(err, "error decoding conf")
return nil, err return nil, err
} }
// if unset we set these defaults
if m["s3.send_content_md5"] == nil {
o.SendContentMd5 = true
}
if m["s3.concurrent_stream_parts"] == nil {
o.ConcurrentStreamParts = true
}
if m["s3.num_threads"] == nil {
o.NumThreads = 4
}
return o, nil return o, nil
} }
+10 -1
View File
@@ -44,7 +44,16 @@ func New(m map[string]interface{}, stream events.Stream) (storage.FS, error) {
return nil, fmt.Errorf("S3 configuration incomplete") return nil, fmt.Errorf("S3 configuration incomplete")
} }
bs, err := blobstore.New(o.S3Endpoint, o.S3Region, o.S3Bucket, o.S3AccessKey, o.S3SecretKey) defaultPutOptions := blobstore.Options{
DisableContentSha256: o.DisableContentSha256,
DisableMultipart: o.DisableMultipart,
SendContentMd5: o.SendContentMd5,
ConcurrentStreamParts: o.ConcurrentStreamParts,
NumThreads: o.NumThreads,
PartSize: o.PartSize,
}
bs, err := blobstore.New(o.S3Endpoint, o.S3Region, o.S3Bucket, o.S3AccessKey, o.S3SecretKey, defaultPutOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+3 -1
View File
@@ -165,7 +165,9 @@ func (cs3 *CS3) Upload(ctx context.Context, req UploadRequest) (*UploadResponse,
} }
if len(req.IfNoneMatch) > 0 { if len(req.IfNoneMatch) > 0 {
if req.IfNoneMatch[0] == "*" { if req.IfNoneMatch[0] == "*" {
ifuReq.Options = &provider.InitiateFileUploadRequest_IfNotExist{} ifuReq.Options = &provider.InitiateFileUploadRequest_IfNotExist{
IfNotExist: true,
}
} }
// else { // else {
// the http upload will carry all if-not-match etags // the http upload will carry all if-not-match etags
+2 -1
View File
@@ -22,6 +22,7 @@ import (
"context" "context"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
) )
type ctxKey struct{} type ctxKey struct{}
@@ -43,5 +44,5 @@ func ContextGetTracerProvider(ctx context.Context) trace.TracerProvider {
if p, ok := ctx.Value(ctxKey{}).(trace.TracerProvider); ok { if p, ok := ctx.Value(ctxKey{}).(trace.TracerProvider); ok {
return p return p
} }
return trace.NewNoopTracerProvider() return noop.NewTracerProvider()
} }
+9
View File
@@ -24,6 +24,7 @@ func WithEnabled() Option {
} }
// WithExporter option // WithExporter option
// Deprecated: unused
func WithExporter(v string) Option { func WithExporter(v string) Option {
return func(o *Options) { return func(o *Options) {
o.Exporter = v o.Exporter = v
@@ -38,6 +39,7 @@ func WithInsecure() Option {
} }
// WithCollector option // WithCollector option
// Deprecated: unused
func WithCollector(v string) Option { func WithCollector(v string) Option {
return func(o *Options) { return func(o *Options) {
o.Collector = v o.Collector = v
@@ -57,3 +59,10 @@ func WithServiceName(v string) Option {
o.ServiceName = v o.ServiceName = v
} }
} }
// WithTransportCredentials option
func WithTransportCredentials(v credentials.TransportCredentials) Option {
return func(o *Options) {
o.TransportCredentials = v
}
}
+12 -92
View File
@@ -21,25 +21,21 @@ package trace
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"os"
"strings"
"sync" "sync"
"time" "time"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace" sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
) )
var ( var (
@@ -66,7 +62,7 @@ func NewTracerProvider(opts ...Option) trace.TracerProvider {
} }
if !options.Enabled { if !options.Enabled {
return trace.NewNoopTracerProvider() return noop.NewTracerProvider()
} }
// default to 'reva' as service name if not set // default to 'reva' as service name if not set
@@ -74,12 +70,7 @@ func NewTracerProvider(opts ...Option) trace.TracerProvider {
options.ServiceName = "reva" options.ServiceName = "reva"
} }
switch options.Exporter { return getOtlpTracerProvider(options)
case "otlp":
return getOtlpTracerProvider(options)
default:
return getJaegerTracerProvider(options)
}
} }
// SetDefaultTracerProvider sets the default trace provider // SetDefaultTracerProvider sets the default trace provider
@@ -97,11 +88,10 @@ func InitDefaultTracerProvider(collector, endpoint string) {
defaultProvider.mutex.Lock() defaultProvider.mutex.Lock()
defer defaultProvider.mutex.Unlock() defer defaultProvider.mutex.Unlock()
if !defaultProvider.initialized { if !defaultProvider.initialized {
SetDefaultTracerProvider(getJaegerTracerProvider(Options{ SetDefaultTracerProvider(getOtlpTracerProvider(Options{
Enabled: true,
Collector: collector,
Endpoint: endpoint, Endpoint: endpoint,
ServiceName: "reva default jaeger provider", ServiceName: "reva default otlp provider",
Insecure: true,
})) }))
} }
} }
@@ -114,89 +104,19 @@ func DefaultProvider() trace.TracerProvider {
return otel.GetTracerProvider() return otel.GetTracerProvider()
} }
// getJaegerTracerProvider returns a new TracerProvider, configure for the specified service
func getJaegerTracerProvider(options Options) trace.TracerProvider {
var exp *jaeger.Exporter
var err error
if options.Endpoint != "" {
var agentHost string
var agentPort string
agentHost, agentPort, err = parseAgentConfig(options.Endpoint)
if err != nil {
panic(err)
}
exp, err = jaeger.New(
jaeger.WithAgentEndpoint(
jaeger.WithAgentHost(agentHost),
jaeger.WithAgentPort(agentPort),
),
)
if err != nil {
panic(err)
}
}
if options.Collector != "" {
exp, err = jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(options.Collector)))
if err != nil {
panic(err)
}
}
hostname, err := os.Hostname()
if err != nil {
panic(err)
}
return sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(options.ServiceName),
semconv.HostNameKey.String(hostname),
)),
)
}
func parseAgentConfig(ae string) (string, string, error) {
u, err := url.Parse(ae)
// as per url.go:
// [...] Trying to parse a hostname and path
// without a scheme is invalid but may not necessarily return an
// error, due to parsing ambiguities.
if err == nil && u.Hostname() != "" && u.Port() != "" {
return u.Hostname(), u.Port(), nil
}
p := strings.Split(ae, ":")
if len(p) != 2 {
return "", "", fmt.Errorf(fmt.Sprintf("invalid agent endpoint `%s`. expected format: `hostname:port`", ae))
}
switch {
case p[0] == "" && p[1] == "": // case ae = ":"
return "", "", fmt.Errorf(fmt.Sprintf("invalid agent endpoint `%s`. expected format: `hostname:port`", ae))
case p[0] == "":
return "", "", fmt.Errorf(fmt.Sprintf("invalid agent endpoint `%s`. expected format: `hostname:port`", ae))
}
return p[0], p[1], nil
}
// getOtelTracerProvider returns a new TracerProvider, configure for the specified service // getOtelTracerProvider returns a new TracerProvider, configure for the specified service
func getOtlpTracerProvider(options Options) trace.TracerProvider { func getOtlpTracerProvider(options Options) trace.TracerProvider {
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()
transportCredentials := options.TransportCredentials
if options.Insecure {
transportCredentials = insecure.NewCredentials()
}
conn, err := grpc.DialContext(ctx, options.Endpoint, conn, err := grpc.DialContext(ctx, options.Endpoint,
// Note the use of insecure transport here. TLS is recommended in production. grpc.WithTransportCredentials(transportCredentials),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
) )
if err != nil { if err != nil {
panic(fmt.Errorf("failed to create gRPC connection to collector: %w", err)) panic(fmt.Errorf("failed to create gRPC connection to endpoint: %w", err))
} }
exporter, err := otlptracegrpc.New( exporter, err := otlptracegrpc.New(
context.Background(), context.Background(),
+5
View File
@@ -0,0 +1,5 @@
# Changelog
## 1.1.0
* added capitalization to native names
+58 -58
View File
@@ -10,37 +10,37 @@ type Language struct {
// Languages is a map of all ISO 639-1 languages using the two character lowercase language code as key. // Languages is a map of all ISO 639-1 languages using the two character lowercase language code as key.
var Languages = map[string]Language{ var Languages = map[string]Language{
"aa": {Code: "aa", Name: "Afar", NativeName: "Afaraf"}, "aa": {Code: "aa", Name: "Afar", NativeName: "Afaraf"},
"ab": {Code: "ab", Name: "Abkhaz", NativeName: "аҧсуа бызшәа"}, "ab": {Code: "ab", Name: "Abkhaz", NativeName: "Аҧсуа бызшәа"},
"ae": {Code: "ae", Name: "Avestan", NativeName: "avesta"}, "ae": {Code: "ae", Name: "Avestan", NativeName: "Avesta"},
"af": {Code: "af", Name: "Afrikaans", NativeName: "Afrikaans"}, "af": {Code: "af", Name: "Afrikaans", NativeName: "Afrikaans"},
"ak": {Code: "ak", Name: "Akan", NativeName: "Akan"}, "ak": {Code: "ak", Name: "Akan", NativeName: "Akan"},
"am": {Code: "am", Name: "Amharic", NativeName: "አማርኛ"}, "am": {Code: "am", Name: "Amharic", NativeName: "አማርኛ"},
"an": {Code: "an", Name: "Aragonese", NativeName: "aragonés"}, "an": {Code: "an", Name: "Aragonese", NativeName: "Aragonés"},
"ar": {Code: "ar", Name: "Arabic", NativeName: "اللغة العربية"}, "ar": {Code: "ar", Name: "Arabic", NativeName: "اللغة العربية"},
"as": {Code: "as", Name: "Assamese", NativeName: "অসমীয়া"}, "as": {Code: "as", Name: "Assamese", NativeName: "অসমীয়া"},
"av": {Code: "av", Name: "Avaric", NativeName: "авар мацӀ"}, "av": {Code: "av", Name: "Avaric", NativeName: "Авар мацӀ"},
"ay": {Code: "ay", Name: "Aymara", NativeName: "aymar aru"}, "ay": {Code: "ay", Name: "Aymara", NativeName: "Aymar aru"},
"az": {Code: "az", Name: "Azerbaijani", NativeName: "azərbaycan dili"}, "az": {Code: "az", Name: "Azerbaijani", NativeName: "Azərbaycan dili"},
"ba": {Code: "ba", Name: "Bashkir", NativeName: "башҡорт теле"}, "ba": {Code: "ba", Name: "Bashkir", NativeName: "Башҡорт теле"},
"be": {Code: "be", Name: "Belarusian", NativeName: "беларуская мова"}, "be": {Code: "be", Name: "Belarusian", NativeName: "Беларуская мова"},
"bg": {Code: "bg", Name: "Bulgarian", NativeName: "български език"}, "bg": {Code: "bg", Name: "Bulgarian", NativeName: "Български език"},
"bh": {Code: "bh", Name: "Bihari", NativeName: "भोजपुरी"}, "bh": {Code: "bh", Name: "Bihari", NativeName: "भोजपुरी"},
"bi": {Code: "bi", Name: "Bislama", NativeName: "Bislama"}, "bi": {Code: "bi", Name: "Bislama", NativeName: "Bislama"},
"bm": {Code: "bm", Name: "Bambara", NativeName: "bamanankan"}, "bm": {Code: "bm", Name: "Bambara", NativeName: "Bamanankan"},
"bn": {Code: "bn", Name: "Bengali", NativeName: "বাংলা"}, "bn": {Code: "bn", Name: "Bengali", NativeName: "বাংলা"},
"bo": {Code: "bo", Name: "Tibetan Standard", NativeName: "བོད་ཡིག"}, "bo": {Code: "bo", Name: "Tibetan Standard", NativeName: "བོད་ཡིག"},
"br": {Code: "br", Name: "Breton", NativeName: "brezhoneg"}, "br": {Code: "br", Name: "Breton", NativeName: "Brezhoneg"},
"bs": {Code: "bs", Name: "Bosnian", NativeName: "bosanski jezik"}, "bs": {Code: "bs", Name: "Bosnian", NativeName: "Bosanski jezik"},
"ca": {Code: "ca", Name: "Catalan", NativeName: "català"}, "ca": {Code: "ca", Name: "Catalan", NativeName: "Català"},
"ce": {Code: "ce", Name: "Chechen", NativeName: "нохчийн мотт"}, "ce": {Code: "ce", Name: "Chechen", NativeName: "Нохчийн мотт"},
"ch": {Code: "ch", Name: "Chamorro", NativeName: "Chamoru"}, "ch": {Code: "ch", Name: "Chamorro", NativeName: "Chamoru"},
"co": {Code: "co", Name: "Corsican", NativeName: "corsu"}, "co": {Code: "co", Name: "Corsican", NativeName: "Corsu"},
"cr": {Code: "cr", Name: "Cree", NativeName: "ᓀᐦᐃᔭᐍᐏᐣ"}, "cr": {Code: "cr", Name: "Cree", NativeName: "ᓀᐦᐃᔭᐍᐏᐣ"},
"cs": {Code: "cs", Name: "Czech", NativeName: "čeština"}, "cs": {Code: "cs", Name: "Czech", NativeName: "Čeština"},
"cu": {Code: "cu", Name: "Old Church Slavonic", NativeName: "ѩзыкъ словѣньскъ"}, "cu": {Code: "cu", Name: "Old Church Slavonic", NativeName: "Ѩзыкъ словѣньскъ"},
"cv": {Code: "cv", Name: "Chuvash", NativeName: "чӑваш чӗлхи"}, "cv": {Code: "cv", Name: "Chuvash", NativeName: "Чӑваш чӗлхи"},
"cy": {Code: "cy", Name: "Welsh", NativeName: "Cymraeg"}, "cy": {Code: "cy", Name: "Welsh", NativeName: "Cymraeg"},
"da": {Code: "da", Name: "Danish", NativeName: "dansk"}, "da": {Code: "da", Name: "Danish", NativeName: "Dansk"},
"de": {Code: "de", Name: "German", NativeName: "Deutsch"}, "de": {Code: "de", Name: "German", NativeName: "Deutsch"},
"dv": {Code: "dv", Name: "Divehi", NativeName: "Dhivehi"}, "dv": {Code: "dv", Name: "Divehi", NativeName: "Dhivehi"},
"dz": {Code: "dz", Name: "Dzongkha", NativeName: "རྫོང་ཁ"}, "dz": {Code: "dz", Name: "Dzongkha", NativeName: "རྫོང་ཁ"},
@@ -49,18 +49,18 @@ var Languages = map[string]Language{
"en": {Code: "en", Name: "English", NativeName: "English"}, "en": {Code: "en", Name: "English", NativeName: "English"},
"eo": {Code: "eo", Name: "Esperanto", NativeName: "Esperanto"}, "eo": {Code: "eo", Name: "Esperanto", NativeName: "Esperanto"},
"es": {Code: "es", Name: "Spanish", NativeName: "Español"}, "es": {Code: "es", Name: "Spanish", NativeName: "Español"},
"et": {Code: "et", Name: "Estonian", NativeName: "eesti"}, "et": {Code: "et", Name: "Estonian", NativeName: "Eesti"},
"eu": {Code: "eu", Name: "Basque", NativeName: "euskara"}, "eu": {Code: "eu", Name: "Basque", NativeName: "Euskara"},
"fa": {Code: "fa", Name: "Persian", NativeName: "فارسی"}, "fa": {Code: "fa", Name: "Persian", NativeName: "فارسی"},
"ff": {Code: "ff", Name: "Fula", NativeName: "Fulfulde"}, "ff": {Code: "ff", Name: "Fula", NativeName: "Fulfulde"},
"fi": {Code: "fi", Name: "Finnish", NativeName: "suomi"}, "fi": {Code: "fi", Name: "Finnish", NativeName: "Suomi"},
"fj": {Code: "fj", Name: "Fijian", NativeName: "Vakaviti"}, "fj": {Code: "fj", Name: "Fijian", NativeName: "Vakaviti"},
"fo": {Code: "fo", Name: "Faroese", NativeName: "føroyskt"}, "fo": {Code: "fo", Name: "Faroese", NativeName: "Føroyskt"},
"fr": {Code: "fr", Name: "French", NativeName: "Français"}, "fr": {Code: "fr", Name: "French", NativeName: "Français"},
"fy": {Code: "fy", Name: "Western Frisian", NativeName: "Frysk"}, "fy": {Code: "fy", Name: "Western Frisian", NativeName: "Frysk"},
"ga": {Code: "ga", Name: "Irish", NativeName: "Gaeilge"}, "ga": {Code: "ga", Name: "Irish", NativeName: "Gaeilge"},
"gd": {Code: "gd", Name: "Scottish Gaelic", NativeName: "Gàidhlig"}, "gd": {Code: "gd", Name: "Scottish Gaelic", NativeName: "Gàidhlig"},
"gl": {Code: "gl", Name: "Galician", NativeName: "galego"}, "gl": {Code: "gl", Name: "Galician", NativeName: "Galego"},
"gn": {Code: "gn", Name: "Guaraní", NativeName: "Avañeẽ"}, "gn": {Code: "gn", Name: "Guaraní", NativeName: "Avañeẽ"},
"gu": {Code: "gu", Name: "Gujarati", NativeName: "ગુજરાતી"}, "gu": {Code: "gu", Name: "Gujarati", NativeName: "ગુજરાતી"},
"gv": {Code: "gv", Name: "Manx", NativeName: "Gaelg"}, "gv": {Code: "gv", Name: "Manx", NativeName: "Gaelg"},
@@ -68,9 +68,9 @@ var Languages = map[string]Language{
"he": {Code: "he", Name: "Hebrew", NativeName: "עברית"}, "he": {Code: "he", Name: "Hebrew", NativeName: "עברית"},
"hi": {Code: "hi", Name: "Hindi", NativeName: "हिन्दी"}, "hi": {Code: "hi", Name: "Hindi", NativeName: "हिन्दी"},
"ho": {Code: "ho", Name: "Hiri Motu", NativeName: "Hiri Motu"}, "ho": {Code: "ho", Name: "Hiri Motu", NativeName: "Hiri Motu"},
"hr": {Code: "hr", Name: "Croatian", NativeName: "hrvatski jezik"}, "hr": {Code: "hr", Name: "Croatian", NativeName: "Hrvatski jezik"},
"ht": {Code: "ht", Name: "Haitian", NativeName: "Kreyòl ayisyen"}, "ht": {Code: "ht", Name: "Haitian", NativeName: "Kreyòl ayisyen"},
"hu": {Code: "hu", Name: "Hungarian", NativeName: "magyar"}, "hu": {Code: "hu", Name: "Hungarian", NativeName: "Magyar"},
"hy": {Code: "hy", Name: "Armenian", NativeName: "Հայերեն"}, "hy": {Code: "hy", Name: "Armenian", NativeName: "Հայերեն"},
"hz": {Code: "hz", Name: "Herero", NativeName: "Otjiherero"}, "hz": {Code: "hz", Name: "Herero", NativeName: "Otjiherero"},
"ia": {Code: "ia", Name: "Interlingua", NativeName: "Interlingua"}, "ia": {Code: "ia", Name: "Interlingua", NativeName: "Interlingua"},
@@ -84,35 +84,35 @@ var Languages = map[string]Language{
"it": {Code: "it", Name: "Italian", NativeName: "Italiano"}, "it": {Code: "it", Name: "Italian", NativeName: "Italiano"},
"iu": {Code: "iu", Name: "Inuktitut", NativeName: "ᐃᓄᒃᑎᑐᑦ"}, "iu": {Code: "iu", Name: "Inuktitut", NativeName: "ᐃᓄᒃᑎᑐᑦ"},
"ja": {Code: "ja", Name: "Japanese", NativeName: "日本語"}, "ja": {Code: "ja", Name: "Japanese", NativeName: "日本語"},
"jv": {Code: "jv", Name: "Javanese", NativeName: "basa Jawa"}, "jv": {Code: "jv", Name: "Javanese", NativeName: "Basa Jawa"},
"ka": {Code: "ka", Name: "Georgian", NativeName: "ართული"}, "ka": {Code: "ka", Name: "Georgian", NativeName: "ართული"},
"kg": {Code: "kg", Name: "Kongo", NativeName: "Kikongo"}, "kg": {Code: "kg", Name: "Kongo", NativeName: "Kikongo"},
"ki": {Code: "ki", Name: "Kikuyu", NativeName: "Gĩkũyũ"}, "ki": {Code: "ki", Name: "Kikuyu", NativeName: "Gĩkũyũ"},
"kj": {Code: "kj", Name: "Kwanyama", NativeName: "Kuanyama"}, "kj": {Code: "kj", Name: "Kwanyama", NativeName: "Kuanyama"},
"kk": {Code: "kk", Name: "Kazakh", NativeName: "қазақ тілі"}, "kk": {Code: "kk", Name: "Kazakh", NativeName: "Қазақ тілі"},
"kl": {Code: "kl", Name: "Kalaallisut", NativeName: "kalaallisut"}, "kl": {Code: "kl", Name: "Kalaallisut", NativeName: "Kalaallisut"},
"km": {Code: "km", Name: "Khmer", NativeName: "ខេមរភាសា"}, "km": {Code: "km", Name: "Khmer", NativeName: "ខេមរភាសា"},
"kn": {Code: "kn", Name: "Kannada", NativeName: "ಕನ್ನಡ"}, "kn": {Code: "kn", Name: "Kannada", NativeName: "ಕನ್ನಡ"},
"ko": {Code: "ko", Name: "Korean", NativeName: "한국어"}, "ko": {Code: "ko", Name: "Korean", NativeName: "한국어"},
"kr": {Code: "kr", Name: "Kanuri", NativeName: "Kanuri"}, "kr": {Code: "kr", Name: "Kanuri", NativeName: "Kanuri"},
"ks": {Code: "ks", Name: "Kashmiri", NativeName: "कश्मीरी"}, "ks": {Code: "ks", Name: "Kashmiri", NativeName: "कश्मीरी"},
"ku": {Code: "ku", Name: "Kurdish", NativeName: "Kurdî"}, "ku": {Code: "ku", Name: "Kurdish", NativeName: "Kurdî"},
"kv": {Code: "kv", Name: "Komi", NativeName: "коми кыв"}, "kv": {Code: "kv", Name: "Komi", NativeName: "Коми кыв"},
"kw": {Code: "kw", Name: "Cornish", NativeName: "Kernewek"}, "kw": {Code: "kw", Name: "Cornish", NativeName: "Kernewek"},
"ky": {Code: "ky", Name: "Kyrgyz", NativeName: "Кыргызча"}, "ky": {Code: "ky", Name: "Kyrgyz", NativeName: "Кыргызча"},
"la": {Code: "la", Name: "Latin", NativeName: "latine"}, "la": {Code: "la", Name: "Latin", NativeName: "Latine"},
"lb": {Code: "lb", Name: "Luxembourgish", NativeName: "Lëtzebuergesch"}, "lb": {Code: "lb", Name: "Luxembourgish", NativeName: "Lëtzebuergesch"},
"lg": {Code: "lg", Name: "Ganda", NativeName: "Luganda"}, "lg": {Code: "lg", Name: "Ganda", NativeName: "Luganda"},
"li": {Code: "li", Name: "Limburgish", NativeName: "Limburgs"}, "li": {Code: "li", Name: "Limburgish", NativeName: "Limburgs"},
"ln": {Code: "ln", Name: "Lingala", NativeName: "Lingála"}, "ln": {Code: "ln", Name: "Lingala", NativeName: "Lingála"},
"lo": {Code: "lo", Name: "Lao", NativeName: "ພາສາ"}, "lo": {Code: "lo", Name: "Lao", NativeName: "ພາສາ"},
"lt": {Code: "lt", Name: "Lithuanian", NativeName: "lietuvių kalba"}, "lt": {Code: "lt", Name: "Lithuanian", NativeName: "Lietuvių kalba"},
"lu": {Code: "lu", Name: "Luba-Katanga", NativeName: "Tshiluba"}, "lu": {Code: "lu", Name: "Luba-Katanga", NativeName: "Tshiluba"},
"lv": {Code: "lv", Name: "Latvian", NativeName: "latviešu valoda"}, "lv": {Code: "lv", Name: "Latvian", NativeName: "Latviešu valoda"},
"mg": {Code: "mg", Name: "Malagasy", NativeName: "fiteny malagasy"}, "mg": {Code: "mg", Name: "Malagasy", NativeName: "Fiteny malagasy"},
"mh": {Code: "mh", Name: "Marshallese", NativeName: "Kajin M̧ajeļ"}, "mh": {Code: "mh", Name: "Marshallese", NativeName: "Kajin M̧ajeļ"},
"mi": {Code: "mi", Name: "Māori", NativeName: "te reo Māori"}, "mi": {Code: "mi", Name: "Māori", NativeName: "Te reo Māori"},
"mk": {Code: "mk", Name: "Macedonian", NativeName: "македонски јазик"}, "mk": {Code: "mk", Name: "Macedonian", NativeName: "Македонски јазик"},
"ml": {Code: "ml", Name: "Malayalam", NativeName: "മലയാളം"}, "ml": {Code: "ml", Name: "Malayalam", NativeName: "മലയാളം"},
"mn": {Code: "mn", Name: "Mongolian", NativeName: "Монгол хэл"}, "mn": {Code: "mn", Name: "Mongolian", NativeName: "Монгол хэл"},
"mr": {Code: "mr", Name: "Marathi", NativeName: "मराठी"}, "mr": {Code: "mr", Name: "Marathi", NativeName: "मराठी"},
@@ -121,61 +121,61 @@ var Languages = map[string]Language{
"my": {Code: "my", Name: "Burmese", NativeName: "ဗမာစာ"}, "my": {Code: "my", Name: "Burmese", NativeName: "ဗမာစာ"},
"na": {Code: "na", Name: "Nauru", NativeName: "Ekakairũ Naoero"}, "na": {Code: "na", Name: "Nauru", NativeName: "Ekakairũ Naoero"},
"nb": {Code: "nb", Name: "Norwegian Bokmål", NativeName: "Norsk bokmål"}, "nb": {Code: "nb", Name: "Norwegian Bokmål", NativeName: "Norsk bokmål"},
"nd": {Code: "nd", Name: "Northern Ndebele", NativeName: "isiNdebele"}, "nd": {Code: "nd", Name: "Northern Ndebele", NativeName: "IsiNdebele"},
"ne": {Code: "ne", Name: "Nepali", NativeName: "नेपाली"}, "ne": {Code: "ne", Name: "Nepali", NativeName: "नेपाली"},
"ng": {Code: "ng", Name: "Ndonga", NativeName: "Owambo"}, "ng": {Code: "ng", Name: "Ndonga", NativeName: "Owambo"},
"nl": {Code: "nl", Name: "Dutch", NativeName: "Nederlands"}, "nl": {Code: "nl", Name: "Dutch", NativeName: "Nederlands"},
"nn": {Code: "nn", Name: "Norwegian Nynorsk", NativeName: "Norsk nynorsk"}, "nn": {Code: "nn", Name: "Norwegian Nynorsk", NativeName: "Norsk nynorsk"},
"no": {Code: "no", Name: "Norwegian", NativeName: "Norsk"}, "no": {Code: "no", Name: "Norwegian", NativeName: "Norsk"},
"nr": {Code: "nr", Name: "Southern Ndebele", NativeName: "isiNdebele"}, "nr": {Code: "nr", Name: "Southern Ndebele", NativeName: "IsiNdebele"},
"nv": {Code: "nv", Name: "Navajo", NativeName: "Diné bizaad"}, "nv": {Code: "nv", Name: "Navajo", NativeName: "Diné bizaad"},
"ny": {Code: "ny", Name: "Chichewa", NativeName: "chiCheŵa"}, "ny": {Code: "ny", Name: "Chichewa", NativeName: "ChiCheŵa"},
"oc": {Code: "oc", Name: "Occitan", NativeName: "occitan"}, "oc": {Code: "oc", Name: "Occitan", NativeName: "Occitan"},
"oj": {Code: "oj", Name: "Ojibwe", NativeName: "ᐊᓂᔑᓈᐯᒧᐎᓐ"}, "oj": {Code: "oj", Name: "Ojibwe", NativeName: "ᐊᓂᔑᓈᐯᒧᐎᓐ"},
"om": {Code: "om", Name: "Oromo", NativeName: "Afaan Oromoo"}, "om": {Code: "om", Name: "Oromo", NativeName: "Afaan Oromoo"},
"or": {Code: "or", Name: "Oriya", NativeName: "ଓଡ଼ିଆ"}, "or": {Code: "or", Name: "Oriya", NativeName: "ଓଡ଼ିଆ"},
"os": {Code: "os", Name: "Ossetian", NativeName: "ирон æвзаг"}, "os": {Code: "os", Name: "Ossetian", NativeName: "Ирон æвзаг"},
"pa": {Code: "pa", Name: "Panjabi", NativeName: "ਪੰਜਾਬੀ"}, "pa": {Code: "pa", Name: "Panjabi", NativeName: "ਪੰਜਾਬੀ"},
"pi": {Code: "pi", Name: "Pāli", NativeName: "पाऴि"}, "pi": {Code: "pi", Name: "Pāli", NativeName: "पाऴि"},
"pl": {Code: "pl", Name: "Polish", NativeName: "język polski"}, "pl": {Code: "pl", Name: "Polish", NativeName: "Język polski"},
"ps": {Code: "ps", Name: "Pashto", NativeName: "پښتو"}, "ps": {Code: "ps", Name: "Pashto", NativeName: "پښتو"},
"pt": {Code: "pt", Name: "Portuguese", NativeName: "Português"}, "pt": {Code: "pt", Name: "Portuguese", NativeName: "Português"},
"qu": {Code: "qu", Name: "Quechua", NativeName: "Runa Simi"}, "qu": {Code: "qu", Name: "Quechua", NativeName: "Runa Simi"},
"rm": {Code: "rm", Name: "Romansh", NativeName: "rumantsch grischun"}, "rm": {Code: "rm", Name: "Romansh", NativeName: "Rumantsch grischun"},
"rn": {Code: "rn", Name: "Kirundi", NativeName: "Ikirundi"}, "rn": {Code: "rn", Name: "Kirundi", NativeName: "Ikirundi"},
"ro": {Code: "ro", Name: "Romanian", NativeName: "Română"}, "ro": {Code: "ro", Name: "Romanian", NativeName: "Română"},
"ru": {Code: "ru", Name: "Russian", NativeName: "Русский"}, "ru": {Code: "ru", Name: "Russian", NativeName: "Русский"},
"rw": {Code: "rw", Name: "Kinyarwanda", NativeName: "Ikinyarwanda"}, "rw": {Code: "rw", Name: "Kinyarwanda", NativeName: "Ikinyarwanda"},
"sa": {Code: "sa", Name: "Sanskrit", NativeName: "संस्कृतम्"}, "sa": {Code: "sa", Name: "Sanskrit", NativeName: "संस्कृतम्"},
"sc": {Code: "sc", Name: "Sardinian", NativeName: "sardu"}, "sc": {Code: "sc", Name: "Sardinian", NativeName: "Sardu"},
"sd": {Code: "sd", Name: "Sindhi", NativeName: "सिन्धी"}, "sd": {Code: "sd", Name: "Sindhi", NativeName: "सिन्धी"},
"se": {Code: "se", Name: "Northern Sami", NativeName: "Davvisámegiella"}, "se": {Code: "se", Name: "Northern Sami", NativeName: "Davvisámegiella"},
"sg": {Code: "sg", Name: "Sango", NativeName: "yângâ tî sängö"}, "sg": {Code: "sg", Name: "Sango", NativeName: "Yângâ tî sängö"},
"si": {Code: "si", Name: "Sinhala", NativeName: "සිංහල"}, "si": {Code: "si", Name: "Sinhala", NativeName: "සිංහල"},
"sk": {Code: "sk", Name: "Slovak", NativeName: "slovenčina"}, "sk": {Code: "sk", Name: "Slovak", NativeName: "Slovenčina"},
"sl": {Code: "sl", Name: "Slovene", NativeName: "slovenski jezik"}, "sl": {Code: "sl", Name: "Slovene", NativeName: "Slovenski jezik"},
"sm": {Code: "sm", Name: "Samoan", NativeName: "gagana faa Samoa"}, "sm": {Code: "sm", Name: "Samoan", NativeName: "Gagana faa Samoa"},
"sn": {Code: "sn", Name: "Shona", NativeName: "chiShona"}, "sn": {Code: "sn", Name: "Shona", NativeName: "ChiShona"},
"so": {Code: "so", Name: "Somali", NativeName: "Soomaaliga"}, "so": {Code: "so", Name: "Somali", NativeName: "Soomaaliga"},
"sq": {Code: "sq", Name: "Albanian", NativeName: "Shqip"}, "sq": {Code: "sq", Name: "Albanian", NativeName: "Shqip"},
"sr": {Code: "sr", Name: "Serbian", NativeName: "српски језик"}, "sr": {Code: "sr", Name: "Serbian", NativeName: "Српски језик"},
"ss": {Code: "ss", Name: "Swati", NativeName: "SiSwati"}, "ss": {Code: "ss", Name: "Swati", NativeName: "SiSwati"},
"st": {Code: "st", Name: "Southern Sotho", NativeName: "Sesotho"}, "st": {Code: "st", Name: "Southern Sotho", NativeName: "Sesotho"},
"su": {Code: "su", Name: "Sundanese", NativeName: "Basa Sunda"}, "su": {Code: "su", Name: "Sundanese", NativeName: "Basa Sunda"},
"sv": {Code: "sv", Name: "Swedish", NativeName: "svenska"}, "sv": {Code: "sv", Name: "Swedish", NativeName: "Svenska"},
"sw": {Code: "sw", Name: "Swahili", NativeName: "Kiswahili"}, "sw": {Code: "sw", Name: "Swahili", NativeName: "Kiswahili"},
"ta": {Code: "ta", Name: "Tamil", NativeName: "தமிழ்"}, "ta": {Code: "ta", Name: "Tamil", NativeName: "தமிழ்"},
"te": {Code: "te", Name: "Telugu", NativeName: "తెలుగు"}, "te": {Code: "te", Name: "Telugu", NativeName: "తెలుగు"},
"tg": {Code: "tg", Name: "Tajik", NativeName: "тоҷикӣ"}, "tg": {Code: "tg", Name: "Tajik", NativeName: "Тоҷикӣ"},
"th": {Code: "th", Name: "Thai", NativeName: "ไทย"}, "th": {Code: "th", Name: "Thai", NativeName: "ไทย"},
"ti": {Code: "ti", Name: "Tigrinya", NativeName: "ትግርኛ"}, "ti": {Code: "ti", Name: "Tigrinya", NativeName: "ትግርኛ"},
"tk": {Code: "tk", Name: "Turkmen", NativeName: "Türkmen"}, "tk": {Code: "tk", Name: "Turkmen", NativeName: "Türkmen"},
"tl": {Code: "tl", Name: "Tagalog", NativeName: "Wikang Tagalog"}, "tl": {Code: "tl", Name: "Tagalog", NativeName: "Wikang Tagalog"},
"tn": {Code: "tn", Name: "Tswana", NativeName: "Setswana"}, "tn": {Code: "tn", Name: "Tswana", NativeName: "Setswana"},
"to": {Code: "to", Name: "Tonga", NativeName: "faka Tonga"}, "to": {Code: "to", Name: "Tonga", NativeName: "Faka Tonga"},
"tr": {Code: "tr", Name: "Turkish", NativeName: "Türkçe"}, "tr": {Code: "tr", Name: "Turkish", NativeName: "Türkçe"},
"ts": {Code: "ts", Name: "Tsonga", NativeName: "Xitsonga"}, "ts": {Code: "ts", Name: "Tsonga", NativeName: "Xitsonga"},
"tt": {Code: "tt", Name: "Tatar", NativeName: "татар теле"}, "tt": {Code: "tt", Name: "Tatar", NativeName: "Татар теле"},
"tw": {Code: "tw", Name: "Twi", NativeName: "Twi"}, "tw": {Code: "tw", Name: "Twi", NativeName: "Twi"},
"ty": {Code: "ty", Name: "Tahitian", NativeName: "Reo Tahiti"}, "ty": {Code: "ty", Name: "Tahitian", NativeName: "Reo Tahiti"},
"ug": {Code: "ug", Name: "Uyghur", NativeName: "ئۇيغۇرچە‎"}, "ug": {Code: "ug", Name: "Uyghur", NativeName: "ئۇيغۇرچە‎"},
@@ -185,12 +185,12 @@ var Languages = map[string]Language{
"ve": {Code: "ve", Name: "Venda", NativeName: "Tshivenḓa"}, "ve": {Code: "ve", Name: "Venda", NativeName: "Tshivenḓa"},
"vi": {Code: "vi", Name: "Vietnamese", NativeName: "Tiếng Việt"}, "vi": {Code: "vi", Name: "Vietnamese", NativeName: "Tiếng Việt"},
"vo": {Code: "vo", Name: "Volapük", NativeName: "Volapük"}, "vo": {Code: "vo", Name: "Volapük", NativeName: "Volapük"},
"wa": {Code: "wa", Name: "Walloon", NativeName: "walon"}, "wa": {Code: "wa", Name: "Walloon", NativeName: "Walon"},
"wo": {Code: "wo", Name: "Wolof", NativeName: "Wollof"}, "wo": {Code: "wo", Name: "Wolof", NativeName: "Wollof"},
"xh": {Code: "xh", Name: "Xhosa", NativeName: "isiXhosa"}, "xh": {Code: "xh", Name: "Xhosa", NativeName: "IsiXhosa"},
"yi": {Code: "yi", Name: "Yiddish", NativeName: "ייִדיש"}, "yi": {Code: "yi", Name: "Yiddish", NativeName: "ייִדיש"},
"yo": {Code: "yo", Name: "Yoruba", NativeName: "Yorùbá"}, "yo": {Code: "yo", Name: "Yoruba", NativeName: "Yorùbá"},
"za": {Code: "za", Name: "Zhuang", NativeName: "Saɯ cueŋƅ"}, "za": {Code: "za", Name: "Zhuang", NativeName: "Saɯ cueŋƅ"},
"zh": {Code: "zh", Name: "Chinese", NativeName: "中文"}, "zh": {Code: "zh", Name: "Chinese", NativeName: "中文"},
"zu": {Code: "zu", Name: "Zulu", NativeName: "isiZulu"}, "zu": {Code: "zu", Name: "Zulu", NativeName: "IsiZulu"},
} }
+9
View File
@@ -23,6 +23,7 @@ Asta Xie <xiemengjun at gmail.com>
Bulat Gaifullin <gaifullinbf at gmail.com> Bulat Gaifullin <gaifullinbf at gmail.com>
Caine Jette <jette at alum.mit.edu> Caine Jette <jette at alum.mit.edu>
Carlos Nieto <jose.carlos at menteslibres.net> Carlos Nieto <jose.carlos at menteslibres.net>
Chris Kirkland <chriskirkland at github.com>
Chris Moos <chris at tech9computers.com> Chris Moos <chris at tech9computers.com>
Craig Wilson <craiggwilson at gmail.com> Craig Wilson <craiggwilson at gmail.com>
Daniel Montoya <dsmontoyam at gmail.com> Daniel Montoya <dsmontoyam at gmail.com>
@@ -45,6 +46,7 @@ Ilia Cimpoes <ichimpoesh at gmail.com>
INADA Naoki <songofacandy at gmail.com> INADA Naoki <songofacandy at gmail.com>
Jacek Szwec <szwec.jacek at gmail.com> Jacek Szwec <szwec.jacek at gmail.com>
James Harr <james.harr at gmail.com> James Harr <james.harr at gmail.com>
Janek Vedock <janekvedock at comcast.net>
Jeff Hodges <jeff at somethingsimilar.com> Jeff Hodges <jeff at somethingsimilar.com>
Jeffrey Charles <jeffreycharles at gmail.com> Jeffrey Charles <jeffreycharles at gmail.com>
Jerome Meyer <jxmeyer at gmail.com> Jerome Meyer <jxmeyer at gmail.com>
@@ -59,12 +61,14 @@ Kamil Dziedzic <kamil at klecza.pl>
Kei Kamikawa <x00.x7f.x86 at gmail.com> Kei Kamikawa <x00.x7f.x86 at gmail.com>
Kevin Malachowski <kevin at chowski.com> Kevin Malachowski <kevin at chowski.com>
Kieron Woodhouse <kieron.woodhouse at infosum.com> Kieron Woodhouse <kieron.woodhouse at infosum.com>
Lance Tian <lance6716 at gmail.com>
Lennart Rudolph <lrudolph at hmc.edu> Lennart Rudolph <lrudolph at hmc.edu>
Leonardo YongUk Kim <dalinaum at gmail.com> Leonardo YongUk Kim <dalinaum at gmail.com>
Linh Tran Tuan <linhduonggnu at gmail.com> Linh Tran Tuan <linhduonggnu at gmail.com>
Lion Yang <lion at aosc.xyz> Lion Yang <lion at aosc.xyz>
Luca Looz <luca.looz92 at gmail.com> Luca Looz <luca.looz92 at gmail.com>
Lucas Liu <extrafliu at gmail.com> Lucas Liu <extrafliu at gmail.com>
Lunny Xiao <xiaolunwen at gmail.com>
Luke Scott <luke at webconnex.com> Luke Scott <luke at webconnex.com>
Maciej Zimnoch <maciej.zimnoch at codilime.com> Maciej Zimnoch <maciej.zimnoch at codilime.com>
Michael Woolnough <michael.woolnough at gmail.com> Michael Woolnough <michael.woolnough at gmail.com>
@@ -74,11 +78,14 @@ Olivier Mengué <dolmen at cpan.org>
oscarzhao <oscarzhaosl at gmail.com> oscarzhao <oscarzhaosl at gmail.com>
Paul Bonser <misterpib at gmail.com> Paul Bonser <misterpib at gmail.com>
Peter Schultz <peter.schultz at classmarkets.com> Peter Schultz <peter.schultz at classmarkets.com>
Phil Porada <philporada at gmail.com>
Rebecca Chin <rchin at pivotal.io> Rebecca Chin <rchin at pivotal.io>
Reed Allman <rdallman10 at gmail.com> Reed Allman <rdallman10 at gmail.com>
Richard Wilkes <wilkes at me.com> Richard Wilkes <wilkes at me.com>
Robert Russell <robert at rrbrussell.com> Robert Russell <robert at rrbrussell.com>
Runrioter Wung <runrioter at gmail.com> Runrioter Wung <runrioter at gmail.com>
Samantha Frank <hello at entropy.cat>
Santhosh Kumar Tekuri <santhosh.tekuri at gmail.com>
Sho Iizuka <sho.i518 at gmail.com> Sho Iizuka <sho.i518 at gmail.com>
Sho Ikeda <suicaicoca at gmail.com> Sho Ikeda <suicaicoca at gmail.com>
Shuode Li <elemount at qq.com> Shuode Li <elemount at qq.com>
@@ -99,12 +106,14 @@ Xiuming Chen <cc at cxm.cc>
Xuehong Chan <chanxuehong at gmail.com> Xuehong Chan <chanxuehong at gmail.com>
Zhenye Xie <xiezhenye at gmail.com> Zhenye Xie <xiezhenye at gmail.com>
Zhixin Wen <john.wenzhixin at gmail.com> Zhixin Wen <john.wenzhixin at gmail.com>
Ziheng Lyu <zihenglv at gmail.com>
# Organizations # Organizations
Barracuda Networks, Inc. Barracuda Networks, Inc.
Counting Ltd. Counting Ltd.
DigitalOcean Inc. DigitalOcean Inc.
dyves labs AG
Facebook Inc. Facebook Inc.
GitHub Inc. GitHub Inc.
Google Inc. Google Inc.
+34
View File
@@ -1,3 +1,37 @@
## Version 1.7.1 (2023-04-25)
Changes:
- bump actions/checkout@v3 and actions/setup-go@v3 (#1375)
- Add go1.20 and mariadb10.11 to the testing matrix (#1403)
- Increase default maxAllowedPacket size. (#1411)
Bugfixes:
- Use SET syntax as specified in the MySQL documentation (#1402)
## Version 1.7 (2022-11-29)
Changes:
- Drop support of Go 1.12 (#1211)
- Refactoring `(*textRows).readRow` in a more clear way (#1230)
- util: Reduce boundary check in escape functions. (#1316)
- enhancement for mysqlConn handleAuthResult (#1250)
New Features:
- support Is comparison on MySQLError (#1210)
- return unsigned in database type name when necessary (#1238)
- Add API to express like a --ssl-mode=PREFERRED MySQL client (#1370)
- Add SQLState to MySQLError (#1321)
Bugfixes:
- Fix parsing 0 year. (#1257)
## Version 1.6 (2021-04-01) ## Version 1.6 (2021-04-01)
Changes: Changes:
+16 -5
View File
@@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
* Optional placeholder interpolation * Optional placeholder interpolation
## Requirements ## Requirements
* Go 1.10 or higher. We aim to support the 3 latest versions of Go. * Go 1.13 or higher. We aim to support the 3 latest versions of Go.
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+) * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
--------------------------------------- ---------------------------------------
@@ -85,7 +85,7 @@ db.SetMaxIdleConns(10)
`db.SetMaxOpenConns()` is highly recommended to limit the number of connection used by the application. There is no recommended limit number because it depends on application and MySQL server. `db.SetMaxOpenConns()` is highly recommended to limit the number of connection used by the application. There is no recommended limit number because it depends on application and MySQL server.
`db.SetMaxIdleConns()` is recommended to be set same to (or greater than) `db.SetMaxOpenConns()`. When it is smaller than `SetMaxOpenConns()`, connections can be opened and closed very frequently than you expect. Idle connections can be closed by the `db.SetConnMaxLifetime()`. If you want to close idle connections more rapidly, you can use `db.SetConnMaxIdleTime()` since Go 1.15. `db.SetMaxIdleConns()` is recommended to be set same to `db.SetMaxOpenConns()`. When it is smaller than `SetMaxOpenConns()`, connections can be opened and closed much more frequently than you expect. Idle connections can be closed by the `db.SetConnMaxLifetime()`. If you want to close idle connections more rapidly, you can use `db.SetConnMaxIdleTime()` since Go 1.15.
### DSN (Data Source Name) ### DSN (Data Source Name)
@@ -157,6 +157,17 @@ Default: false
`allowCleartextPasswords=true` allows using the [cleartext client side plugin](https://dev.mysql.com/doc/en/cleartext-pluggable-authentication.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network. `allowCleartextPasswords=true` allows using the [cleartext client side plugin](https://dev.mysql.com/doc/en/cleartext-pluggable-authentication.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
##### `allowFallbackToPlaintext`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowFallbackToPlaintext=true` acts like a `--ssl-mode=PREFERRED` MySQL client as described in [Command Options for Connecting to the Server](https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode)
##### `allowNativePasswords` ##### `allowNativePasswords`
``` ```
@@ -271,10 +282,10 @@ Please keep in mind, that param values must be [url.QueryEscape](https://golang.
##### `maxAllowedPacket` ##### `maxAllowedPacket`
``` ```
Type: decimal number Type: decimal number
Default: 4194304 Default: 64*1024*1024
``` ```
Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*. Max packet size allowed in bytes. The default value is 64 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
##### `multiStatements` ##### `multiStatements`
@@ -454,7 +465,7 @@ user:password@/
The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively. The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively.
## `ColumnType` Support ## `ColumnType` Support
This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported. This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported. All Unsigned database type names will be returned `UNSIGNED ` with `INT`, `TINYINT`, `SMALLINT`, `BIGINT`.
## `context.Context` Support ## `context.Context` Support
Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts. Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
+19
View File
@@ -0,0 +1,19 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
//
// Copyright 2022 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build go1.19
// +build go1.19
package mysql
import "sync/atomic"
/******************************************************************************
* Sync utils *
******************************************************************************/
type atomicBool = atomic.Bool
+47
View File
@@ -0,0 +1,47 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package.
//
// Copyright 2022 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !go1.19
// +build !go1.19
package mysql
import "sync/atomic"
/******************************************************************************
* Sync utils *
******************************************************************************/
// atomicBool is an implementation of atomic.Bool for older version of Go.
// it is a wrapper around uint32 for usage as a boolean value with
// atomic access.
type atomicBool struct {
_ noCopy
value uint32
}
// Load returns whether the current boolean value is true
func (ab *atomicBool) Load() bool {
return atomic.LoadUint32(&ab.value) > 0
}
// Store sets the value of the bool regardless of the previous value
func (ab *atomicBool) Store(value bool) {
if value {
atomic.StoreUint32(&ab.value, 1)
} else {
atomic.StoreUint32(&ab.value, 0)
}
}
// Swap sets the value of the bool and returns the old value.
func (ab *atomicBool) Swap(value bool) bool {
if value {
return atomic.SwapUint32(&ab.value, 1) > 0
}
return atomic.SwapUint32(&ab.value, 0) > 0
}
+34 -22
View File
@@ -33,27 +33,26 @@ var (
// Note: The provided rsa.PublicKey instance is exclusively owned by the driver // Note: The provided rsa.PublicKey instance is exclusively owned by the driver
// after registering it and may not be modified. // after registering it and may not be modified.
// //
// data, err := ioutil.ReadFile("mykey.pem") // data, err := ioutil.ReadFile("mykey.pem")
// if err != nil { // if err != nil {
// log.Fatal(err) // log.Fatal(err)
// } // }
// //
// block, _ := pem.Decode(data) // block, _ := pem.Decode(data)
// if block == nil || block.Type != "PUBLIC KEY" { // if block == nil || block.Type != "PUBLIC KEY" {
// log.Fatal("failed to decode PEM block containing public key") // log.Fatal("failed to decode PEM block containing public key")
// } // }
// //
// pub, err := x509.ParsePKIXPublicKey(block.Bytes) // pub, err := x509.ParsePKIXPublicKey(block.Bytes)
// if err != nil { // if err != nil {
// log.Fatal(err) // log.Fatal(err)
// } // }
//
// if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
// mysql.RegisterServerPubKey("mykey", rsaPubKey)
// } else {
// log.Fatal("not a RSA public key")
// }
// //
// if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
// mysql.RegisterServerPubKey("mykey", rsaPubKey)
// } else {
// log.Fatal("not a RSA public key")
// }
func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) { func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
serverPubKeyLock.Lock() serverPubKeyLock.Lock()
if serverPubKeyRegistry == nil { if serverPubKeyRegistry == nil {
@@ -274,7 +273,9 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
if len(mc.cfg.Passwd) == 0 { if len(mc.cfg.Passwd) == 0 {
return []byte{0}, nil return []byte{0}, nil
} }
if mc.cfg.tls != nil || mc.cfg.Net == "unix" { // unlike caching_sha2_password, sha256_password does not accept
// cleartext password on unix transport.
if mc.cfg.TLS != nil {
// write cleartext auth packet // write cleartext auth packet
return append([]byte(mc.cfg.Passwd), 0), nil return append([]byte(mc.cfg.Passwd), 0), nil
} }
@@ -350,7 +351,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
} }
case cachingSha2PasswordPerformFullAuthentication: case cachingSha2PasswordPerformFullAuthentication:
if mc.cfg.tls != nil || mc.cfg.Net == "unix" { if mc.cfg.TLS != nil || mc.cfg.Net == "unix" {
// write cleartext auth packet // write cleartext auth packet
err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0)) err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0))
if err != nil { if err != nil {
@@ -365,13 +366,20 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
return err return err
} }
data[4] = cachingSha2PasswordRequestPublicKey data[4] = cachingSha2PasswordRequestPublicKey
mc.writePacket(data) err = mc.writePacket(data)
if err != nil {
return err
}
// parse public key
if data, err = mc.readPacket(); err != nil { if data, err = mc.readPacket(); err != nil {
return err return err
} }
if data[0] != iAuthMoreData {
return fmt.Errorf("unexpect resp from server for caching_sha2_password perform full authentication")
}
// parse public key
block, rest := pem.Decode(data[1:]) block, rest := pem.Decode(data[1:])
if block == nil { if block == nil {
return fmt.Errorf("No Pem data found, data: %s", rest) return fmt.Errorf("No Pem data found, data: %s", rest)
@@ -404,6 +412,10 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
return nil // auth successful return nil // auth successful
default: default:
block, _ := pem.Decode(authData) block, _ := pem.Decode(authData)
if block == nil {
return fmt.Errorf("no Pem data found, data: %s", authData)
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes) pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil { if err != nil {
return err return err
+2 -1
View File
@@ -13,7 +13,8 @@ const binaryCollation = "binary"
// A list of available collations mapped to the internal ID. // A list of available collations mapped to the internal ID.
// To update this map use the following MySQL query: // To update this map use the following MySQL query:
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS WHERE ID<256 ORDER BY ID //
// SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS WHERE ID<256 ORDER BY ID
// //
// Handshake packet have only 1 byte for collation_id. So we can't use collations with ID > 255. // Handshake packet have only 1 byte for collation_id. So we can't use collations with ID > 255.
// //
+1
View File
@@ -6,6 +6,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || illumos
// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos // +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
package mysql package mysql
+1
View File
@@ -6,6 +6,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !linux && !darwin && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !illumos
// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos // +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
package mysql package mysql
+13 -13
View File
@@ -71,10 +71,10 @@ func (mc *mysqlConn) handleParams() (err error) {
cmdSet.Grow(4 + len(param) + 1 + len(val) + 30*(len(mc.cfg.Params)-1)) cmdSet.Grow(4 + len(param) + 1 + len(val) + 30*(len(mc.cfg.Params)-1))
cmdSet.WriteString("SET ") cmdSet.WriteString("SET ")
} else { } else {
cmdSet.WriteByte(',') cmdSet.WriteString(", ")
} }
cmdSet.WriteString(param) cmdSet.WriteString(param)
cmdSet.WriteByte('=') cmdSet.WriteString(" = ")
cmdSet.WriteString(val) cmdSet.WriteString(val)
} }
} }
@@ -104,7 +104,7 @@ func (mc *mysqlConn) Begin() (driver.Tx, error) {
} }
func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) { func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
if mc.closed.IsSet() { if mc.closed.Load() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
@@ -123,7 +123,7 @@ func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
func (mc *mysqlConn) Close() (err error) { func (mc *mysqlConn) Close() (err error) {
// Makes Close idempotent // Makes Close idempotent
if !mc.closed.IsSet() { if !mc.closed.Load() {
err = mc.writeCommandPacket(comQuit) err = mc.writeCommandPacket(comQuit)
} }
@@ -137,7 +137,7 @@ func (mc *mysqlConn) Close() (err error) {
// is called before auth or on auth failure because MySQL will have already // is called before auth or on auth failure because MySQL will have already
// closed the network connection. // closed the network connection.
func (mc *mysqlConn) cleanup() { func (mc *mysqlConn) cleanup() {
if !mc.closed.TrySet(true) { if mc.closed.Swap(true) {
return return
} }
@@ -152,7 +152,7 @@ func (mc *mysqlConn) cleanup() {
} }
func (mc *mysqlConn) error() error { func (mc *mysqlConn) error() error {
if mc.closed.IsSet() { if mc.closed.Load() {
if err := mc.canceled.Value(); err != nil { if err := mc.canceled.Value(); err != nil {
return err return err
} }
@@ -162,7 +162,7 @@ func (mc *mysqlConn) error() error {
} }
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
if mc.closed.IsSet() { if mc.closed.Load() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
@@ -295,7 +295,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
} }
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) { func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
if mc.closed.IsSet() { if mc.closed.Load() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
@@ -356,7 +356,7 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
} }
func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) { func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
if mc.closed.IsSet() { if mc.closed.Load() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
@@ -450,7 +450,7 @@ func (mc *mysqlConn) finish() {
// Ping implements driver.Pinger interface // Ping implements driver.Pinger interface
func (mc *mysqlConn) Ping(ctx context.Context) (err error) { func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
if mc.closed.IsSet() { if mc.closed.Load() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return driver.ErrBadConn return driver.ErrBadConn
} }
@@ -469,7 +469,7 @@ func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
// BeginTx implements driver.ConnBeginTx interface // BeginTx implements driver.ConnBeginTx interface
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if mc.closed.IsSet() { if mc.closed.Load() {
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
@@ -636,7 +636,7 @@ func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
// ResetSession implements driver.SessionResetter. // ResetSession implements driver.SessionResetter.
// (From Go 1.10) // (From Go 1.10)
func (mc *mysqlConn) ResetSession(ctx context.Context) error { func (mc *mysqlConn) ResetSession(ctx context.Context) error {
if mc.closed.IsSet() { if mc.closed.Load() {
return driver.ErrBadConn return driver.ErrBadConn
} }
mc.reset = true mc.reset = true
@@ -646,5 +646,5 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
// IsValid implements driver.Validator interface // IsValid implements driver.Validator interface
// (From Go 1.15) // (From Go 1.15)
func (mc *mysqlConn) IsValid() bool { func (mc *mysqlConn) IsValid() bool {
return !mc.closed.IsSet() return !mc.closed.Load()
} }
+1 -1
View File
@@ -10,7 +10,7 @@ package mysql
const ( const (
defaultAuthPlugin = "mysql_native_password" defaultAuthPlugin = "mysql_native_password"
defaultMaxAllowedPacket = 4 << 20 // 4 MiB defaultMaxAllowedPacket = 64 << 20 // 64 MiB. See https://github.com/go-sql-driver/mysql/issues/1355
minProtocolVersion = 10 minProtocolVersion = 10
maxPacketSize = 1<<24 - 1 maxPacketSize = 1<<24 - 1
timeFormat = "2006-01-02 15:04:05.999999" timeFormat = "2006-01-02 15:04:05.999999"
+3 -3
View File
@@ -8,10 +8,10 @@
// //
// The driver should be used via the database/sql package: // The driver should be used via the database/sql package:
// //
// import "database/sql" // import "database/sql"
// import _ "github.com/go-sql-driver/mysql" // import _ "github.com/go-sql-driver/mysql"
// //
// db, err := sql.Open("mysql", "user:password@/dbname") // db, err := sql.Open("mysql", "user:password@/dbname")
// //
// See https://github.com/go-sql-driver/mysql#usage for details // See https://github.com/go-sql-driver/mysql#usage for details
package mysql package mysql
+45 -28
View File
@@ -46,22 +46,23 @@ type Config struct {
ServerPubKey string // Server public key name ServerPubKey string // Server public key name
pubKey *rsa.PublicKey // Server public key pubKey *rsa.PublicKey // Server public key
TLSConfig string // TLS configuration name TLSConfig string // TLS configuration name
tls *tls.Config // TLS configuration TLS *tls.Config // TLS configuration, its priority is higher than TLSConfig
Timeout time.Duration // Dial timeout Timeout time.Duration // Dial timeout
ReadTimeout time.Duration // I/O read timeout ReadTimeout time.Duration // I/O read timeout
WriteTimeout time.Duration // I/O write timeout WriteTimeout time.Duration // I/O write timeout
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
AllowCleartextPasswords bool // Allows the cleartext client side plugin AllowCleartextPasswords bool // Allows the cleartext client side plugin
AllowNativePasswords bool // Allows the native password authentication method AllowFallbackToPlaintext bool // Allows fallback to unencrypted connection if server does not support TLS
AllowOldPasswords bool // Allows the old insecure password method AllowNativePasswords bool // Allows the native password authentication method
CheckConnLiveness bool // Check connections for liveness before using them AllowOldPasswords bool // Allows the old insecure password method
ClientFoundRows bool // Return number of matching rows instead of rows changed CheckConnLiveness bool // Check connections for liveness before using them
ColumnsWithAlias bool // Prepend table alias to column names ClientFoundRows bool // Return number of matching rows instead of rows changed
InterpolateParams bool // Interpolate placeholders into query string ColumnsWithAlias bool // Prepend table alias to column names
MultiStatements bool // Allow multiple statements in one query InterpolateParams bool // Interpolate placeholders into query string
ParseTime bool // Parse time values to time.Time MultiStatements bool // Allow multiple statements in one query
RejectReadOnly bool // Reject read-only connections ParseTime bool // Parse time values to time.Time
RejectReadOnly bool // Reject read-only connections
} }
// NewConfig creates a new Config and sets default values. // NewConfig creates a new Config and sets default values.
@@ -77,8 +78,8 @@ func NewConfig() *Config {
func (cfg *Config) Clone() *Config { func (cfg *Config) Clone() *Config {
cp := *cfg cp := *cfg
if cp.tls != nil { if cp.TLS != nil {
cp.tls = cfg.tls.Clone() cp.TLS = cfg.TLS.Clone()
} }
if len(cp.Params) > 0 { if len(cp.Params) > 0 {
cp.Params = make(map[string]string, len(cfg.Params)) cp.Params = make(map[string]string, len(cfg.Params))
@@ -119,24 +120,29 @@ func (cfg *Config) normalize() error {
cfg.Addr = ensureHavePort(cfg.Addr) cfg.Addr = ensureHavePort(cfg.Addr)
} }
switch cfg.TLSConfig { if cfg.TLS == nil {
case "false", "": switch cfg.TLSConfig {
// don't set anything case "false", "":
case "true": // don't set anything
cfg.tls = &tls.Config{} case "true":
case "skip-verify", "preferred": cfg.TLS = &tls.Config{}
cfg.tls = &tls.Config{InsecureSkipVerify: true} case "skip-verify":
default: cfg.TLS = &tls.Config{InsecureSkipVerify: true}
cfg.tls = getTLSConfigClone(cfg.TLSConfig) case "preferred":
if cfg.tls == nil { cfg.TLS = &tls.Config{InsecureSkipVerify: true}
return errors.New("invalid value / unknown config name: " + cfg.TLSConfig) cfg.AllowFallbackToPlaintext = true
default:
cfg.TLS = getTLSConfigClone(cfg.TLSConfig)
if cfg.TLS == nil {
return errors.New("invalid value / unknown config name: " + cfg.TLSConfig)
}
} }
} }
if cfg.tls != nil && cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify { if cfg.TLS != nil && cfg.TLS.ServerName == "" && !cfg.TLS.InsecureSkipVerify {
host, _, err := net.SplitHostPort(cfg.Addr) host, _, err := net.SplitHostPort(cfg.Addr)
if err == nil { if err == nil {
cfg.tls.ServerName = host cfg.TLS.ServerName = host
} }
} }
@@ -204,6 +210,10 @@ func (cfg *Config) FormatDSN() string {
writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true") writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true")
} }
if cfg.AllowFallbackToPlaintext {
writeDSNParam(&buf, &hasParam, "allowFallbackToPlaintext", "true")
}
if !cfg.AllowNativePasswords { if !cfg.AllowNativePasswords {
writeDSNParam(&buf, &hasParam, "allowNativePasswords", "false") writeDSNParam(&buf, &hasParam, "allowNativePasswords", "false")
} }
@@ -391,6 +401,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return errors.New("invalid bool value: " + value) return errors.New("invalid bool value: " + value)
} }
// Allow fallback to unencrypted connection if server does not support TLS
case "allowFallbackToPlaintext":
var isBool bool
cfg.AllowFallbackToPlaintext, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Use native password authentication // Use native password authentication
case "allowNativePasswords": case "allowNativePasswords":
var isBool bool var isBool bool
@@ -426,7 +444,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
// Collation // Collation
case "collation": case "collation":
cfg.Collation = value cfg.Collation = value
break
case "columnsWithAlias": case "columnsWithAlias":
var isBool bool var isBool bool
+15 -3
View File
@@ -27,7 +27,7 @@ var (
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+") ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
ErrPktSync = errors.New("commands out of sync. You can't run this command now") ErrPktSync = errors.New("commands out of sync. You can't run this command now")
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?") ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server") ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the `Config.MaxAllowedPacket`")
ErrBusyBuffer = errors.New("busy buffer") ErrBusyBuffer = errors.New("busy buffer")
// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet. // errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
@@ -56,10 +56,22 @@ func SetLogger(logger Logger) error {
// MySQLError is an error type which represents a single MySQL error // MySQLError is an error type which represents a single MySQL error
type MySQLError struct { type MySQLError struct {
Number uint16 Number uint16
Message string SQLState [5]byte
Message string
} }
func (me *MySQLError) Error() string { func (me *MySQLError) Error() string {
if me.SQLState != [5]byte{} {
return fmt.Sprintf("Error %d (%s): %s", me.Number, me.SQLState, me.Message)
}
return fmt.Sprintf("Error %d: %s", me.Number, me.Message) return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
} }
func (me *MySQLError) Is(err error) bool {
if merr, ok := err.(*MySQLError); ok {
return merr.Number == me.Number
}
return false
}
+13 -1
View File
@@ -41,6 +41,9 @@ func (mf *mysqlField) typeDatabaseName() string {
case fieldTypeJSON: case fieldTypeJSON:
return "JSON" return "JSON"
case fieldTypeLong: case fieldTypeLong:
if mf.flags&flagUnsigned != 0 {
return "UNSIGNED INT"
}
return "INT" return "INT"
case fieldTypeLongBLOB: case fieldTypeLongBLOB:
if mf.charSet != collations[binaryCollation] { if mf.charSet != collations[binaryCollation] {
@@ -48,6 +51,9 @@ func (mf *mysqlField) typeDatabaseName() string {
} }
return "LONGBLOB" return "LONGBLOB"
case fieldTypeLongLong: case fieldTypeLongLong:
if mf.flags&flagUnsigned != 0 {
return "UNSIGNED BIGINT"
}
return "BIGINT" return "BIGINT"
case fieldTypeMediumBLOB: case fieldTypeMediumBLOB:
if mf.charSet != collations[binaryCollation] { if mf.charSet != collations[binaryCollation] {
@@ -63,6 +69,9 @@ func (mf *mysqlField) typeDatabaseName() string {
case fieldTypeSet: case fieldTypeSet:
return "SET" return "SET"
case fieldTypeShort: case fieldTypeShort:
if mf.flags&flagUnsigned != 0 {
return "UNSIGNED SMALLINT"
}
return "SMALLINT" return "SMALLINT"
case fieldTypeString: case fieldTypeString:
if mf.charSet == collations[binaryCollation] { if mf.charSet == collations[binaryCollation] {
@@ -74,6 +83,9 @@ func (mf *mysqlField) typeDatabaseName() string {
case fieldTypeTimestamp: case fieldTypeTimestamp:
return "TIMESTAMP" return "TIMESTAMP"
case fieldTypeTiny: case fieldTypeTiny:
if mf.flags&flagUnsigned != 0 {
return "UNSIGNED TINYINT"
}
return "TINYINT" return "TINYINT"
case fieldTypeTinyBLOB: case fieldTypeTinyBLOB:
if mf.charSet != collations[binaryCollation] { if mf.charSet != collations[binaryCollation] {
@@ -106,7 +118,7 @@ var (
scanTypeInt64 = reflect.TypeOf(int64(0)) scanTypeInt64 = reflect.TypeOf(int64(0))
scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{}) scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
scanTypeNullInt = reflect.TypeOf(sql.NullInt64{}) scanTypeNullInt = reflect.TypeOf(sql.NullInt64{})
scanTypeNullTime = reflect.TypeOf(nullTime{}) scanTypeNullTime = reflect.TypeOf(sql.NullTime{})
scanTypeUint8 = reflect.TypeOf(uint8(0)) scanTypeUint8 = reflect.TypeOf(uint8(0))
scanTypeUint16 = reflect.TypeOf(uint16(0)) scanTypeUint16 = reflect.TypeOf(uint16(0))
scanTypeUint32 = reflect.TypeOf(uint32(0)) scanTypeUint32 = reflect.TypeOf(uint32(0))
+1
View File
@@ -6,6 +6,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this file, // License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build gofuzz
// +build gofuzz // +build gofuzz
package mysql package mysql
+16 -16
View File
@@ -28,12 +28,11 @@ var (
// Alternatively you can allow the use of all local files with // Alternatively you can allow the use of all local files with
// the DSN parameter 'allowAllFiles=true' // the DSN parameter 'allowAllFiles=true'
// //
// filePath := "/home/gopher/data.csv" // filePath := "/home/gopher/data.csv"
// mysql.RegisterLocalFile(filePath) // mysql.RegisterLocalFile(filePath)
// err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo") // err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo")
// if err != nil { // if err != nil {
// ... // ...
//
func RegisterLocalFile(filePath string) { func RegisterLocalFile(filePath string) {
fileRegisterLock.Lock() fileRegisterLock.Lock()
// lazy map init // lazy map init
@@ -58,15 +57,14 @@ func DeregisterLocalFile(filePath string) {
// If the handler returns a io.ReadCloser Close() is called when the // If the handler returns a io.ReadCloser Close() is called when the
// request is finished. // request is finished.
// //
// mysql.RegisterReaderHandler("data", func() io.Reader { // mysql.RegisterReaderHandler("data", func() io.Reader {
// var csvReader io.Reader // Some Reader that returns CSV data // var csvReader io.Reader // Some Reader that returns CSV data
// ... // Open Reader here // ... // Open Reader here
// return csvReader // return csvReader
// }) // })
// err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo") // err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo")
// if err != nil { // if err != nil {
// ... // ...
//
func RegisterReaderHandler(name string, handler func() io.Reader) { func RegisterReaderHandler(name string, handler func() io.Reader) {
readerRegisterLock.Lock() readerRegisterLock.Lock()
// lazy map init // lazy map init
@@ -93,10 +91,12 @@ func deferredClose(err *error, closer io.Closer) {
} }
} }
const defaultPacketSize = 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP
func (mc *mysqlConn) handleInFileRequest(name string) (err error) { func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
var rdr io.Reader var rdr io.Reader
var data []byte var data []byte
packetSize := 16 * 1024 // 16KB is small enough for disk readahead and large enough for TCP packetSize := defaultPacketSize
if mc.maxWriteSize < packetSize { if mc.maxWriteSize < packetSize {
packetSize = mc.maxWriteSize packetSize = mc.maxWriteSize
} }
+21
View File
@@ -9,11 +9,32 @@
package mysql package mysql
import ( import (
"database/sql"
"database/sql/driver" "database/sql/driver"
"fmt" "fmt"
"time" "time"
) )
// NullTime represents a time.Time that may be NULL.
// NullTime implements the Scanner interface so
// it can be used as a scan destination:
//
// var nt NullTime
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
// ...
// if nt.Valid {
// // use nt.Time
// } else {
// // NULL value
// }
//
// # This NullTime implementation is not driver-specific
//
// Deprecated: NullTime doesn't honor the loc DSN parameter.
// NullTime.Scan interprets a time as UTC, not the loc DSN parameter.
// Use sql.NullTime instead.
type NullTime sql.NullTime
// Scan implements the Scanner interface. // Scan implements the Scanner interface.
// The value type must be time.Time or string / []byte (formatted time-string), // The value type must be time.Time or string / []byte (formatted time-string),
// otherwise Scan fails. // otherwise Scan fails.
-40
View File
@@ -1,40 +0,0 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.13
package mysql
import (
"database/sql"
)
// NullTime represents a time.Time that may be NULL.
// NullTime implements the Scanner interface so
// it can be used as a scan destination:
//
// var nt NullTime
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
// ...
// if nt.Valid {
// // use nt.Time
// } else {
// // NULL value
// }
//
// This NullTime implementation is not driver-specific
//
// Deprecated: NullTime doesn't honor the loc DSN parameter.
// NullTime.Scan interprets a time as UTC, not the loc DSN parameter.
// Use sql.NullTime instead.
type NullTime sql.NullTime
// for internal use.
// the mysql package uses sql.NullTime if it is available.
// if not, the package uses mysql.NullTime.
type nullTime = sql.NullTime // sql.NullTime is available
-39
View File
@@ -1,39 +0,0 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build !go1.13
package mysql
import (
"time"
)
// NullTime represents a time.Time that may be NULL.
// NullTime implements the Scanner interface so
// it can be used as a scan destination:
//
// var nt NullTime
// err := db.QueryRow("SELECT time FROM foo WHERE id=?", id).Scan(&nt)
// ...
// if nt.Valid {
// // use nt.Time
// } else {
// // NULL value
// }
//
// This NullTime implementation is not driver-specific
type NullTime struct {
Time time.Time
Valid bool // Valid is true if Time is not NULL
}
// for internal use.
// the mysql package uses sql.NullTime if it is available.
// if not, the package uses mysql.NullTime.
type nullTime = NullTime // sql.NullTime is not available
+45 -45
View File
@@ -110,14 +110,13 @@ func (mc *mysqlConn) writePacket(data []byte) error {
conn = mc.rawConn conn = mc.rawConn
} }
var err error var err error
// If this connection has a ReadTimeout which we've been setting on if mc.cfg.CheckConnLiveness {
// reads, reset it to its default value before we attempt a non-blocking if mc.cfg.ReadTimeout != 0 {
// read, otherwise the scheduler will just time us out before we can read err = conn.SetReadDeadline(time.Now().Add(mc.cfg.ReadTimeout))
if mc.cfg.ReadTimeout != 0 { }
err = conn.SetReadDeadline(time.Time{}) if err == nil {
} err = connCheck(conn)
if err == nil && mc.cfg.CheckConnLiveness { }
err = connCheck(conn)
} }
if err != nil { if err != nil {
errLog.Print("closing bad idle connection: ", err) errLog.Print("closing bad idle connection: ", err)
@@ -223,9 +222,9 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
if mc.flags&clientProtocol41 == 0 { if mc.flags&clientProtocol41 == 0 {
return nil, "", ErrOldProtocol return nil, "", ErrOldProtocol
} }
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil { if mc.flags&clientSSL == 0 && mc.cfg.TLS != nil {
if mc.cfg.TLSConfig == "preferred" { if mc.cfg.AllowFallbackToPlaintext {
mc.cfg.tls = nil mc.cfg.TLS = nil
} else { } else {
return nil, "", ErrNoTLS return nil, "", ErrNoTLS
} }
@@ -293,7 +292,7 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
} }
// To enable TLS / SSL // To enable TLS / SSL
if mc.cfg.tls != nil { if mc.cfg.TLS != nil {
clientFlags |= clientSSL clientFlags |= clientSSL
} }
@@ -357,14 +356,14 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
// SSL Connection Request Packet // SSL Connection Request Packet
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest
if mc.cfg.tls != nil { if mc.cfg.TLS != nil {
// Send TLS / SSL request packet // Send TLS / SSL request packet
if err := mc.writePacket(data[:(4+4+1+23)+4]); err != nil { if err := mc.writePacket(data[:(4+4+1+23)+4]); err != nil {
return err return err
} }
// Switch to TLS // Switch to TLS
tlsConn := tls.Client(mc.netConn, mc.cfg.tls) tlsConn := tls.Client(mc.netConn, mc.cfg.TLS)
if err := tlsConn.Handshake(); err != nil { if err := tlsConn.Handshake(); err != nil {
return err return err
} }
@@ -588,19 +587,20 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
return driver.ErrBadConn return driver.ErrBadConn
} }
me := &MySQLError{Number: errno}
pos := 3 pos := 3
// SQL State [optional: # + 5bytes string] // SQL State [optional: # + 5bytes string]
if data[3] == 0x23 { if data[3] == 0x23 {
//sqlstate := string(data[4 : 4+5]) copy(me.SQLState[:], data[4:4+5])
pos = 9 pos = 9
} }
// Error Message [string] // Error Message [string]
return &MySQLError{ me.Message = string(data[pos:])
Number: errno,
Message: string(data[pos:]), return me
}
} }
func readStatus(b []byte) statusFlag { func readStatus(b []byte) statusFlag {
@@ -761,40 +761,40 @@ func (rows *textRows) readRow(dest []driver.Value) error {
} }
// RowSet Packet // RowSet Packet
var n int var (
var isNull bool n int
pos := 0 isNull bool
pos int = 0
)
for i := range dest { for i := range dest {
// Read bytes and convert to string // Read bytes and convert to string
dest[i], isNull, n, err = readLengthEncodedString(data[pos:]) dest[i], isNull, n, err = readLengthEncodedString(data[pos:])
pos += n pos += n
if err == nil {
if !isNull {
if !mc.parseTime {
continue
} else {
switch rows.rs.columns[i].fieldType {
case fieldTypeTimestamp, fieldTypeDateTime,
fieldTypeDate, fieldTypeNewDate:
dest[i], err = parseDateTime(
dest[i].([]byte),
mc.cfg.Loc,
)
if err == nil {
continue
}
default:
continue
}
}
} else { if err != nil {
dest[i] = nil return err
continue }
if isNull {
dest[i] = nil
continue
}
if !mc.parseTime {
continue
}
// Parse time field
switch rows.rs.columns[i].fieldType {
case fieldTypeTimestamp,
fieldTypeDateTime,
fieldTypeDate,
fieldTypeNewDate:
if dest[i], err = parseDateTime(dest[i].([]byte), mc.cfg.Loc); err != nil {
return err
} }
} }
return err // err != nil
} }
return nil return nil
+4 -4
View File
@@ -23,7 +23,7 @@ type mysqlStmt struct {
} }
func (stmt *mysqlStmt) Close() error { func (stmt *mysqlStmt) Close() error {
if stmt.mc == nil || stmt.mc.closed.IsSet() { if stmt.mc == nil || stmt.mc.closed.Load() {
// driver.Stmt.Close can be called more than once, thus this function // driver.Stmt.Close can be called more than once, thus this function
// has to be idempotent. // has to be idempotent.
// See also Issue #450 and golang/go#16019. // See also Issue #450 and golang/go#16019.
@@ -50,7 +50,7 @@ func (stmt *mysqlStmt) CheckNamedValue(nv *driver.NamedValue) (err error) {
} }
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) { func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
if stmt.mc.closed.IsSet() { if stmt.mc.closed.Load() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
@@ -98,7 +98,7 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
} }
func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) { func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
if stmt.mc.closed.IsSet() { if stmt.mc.closed.Load() {
errLog.Print(ErrInvalidConn) errLog.Print(ErrInvalidConn)
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
@@ -157,7 +157,7 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
if driver.IsValue(sv) { if driver.IsValue(sv) {
return sv, nil return sv, nil
} }
// A value returend from the Valuer interface can be "a type handled by // A value returned from the Valuer interface can be "a type handled by
// a database driver's NamedValueChecker interface" so we should accept // a database driver's NamedValueChecker interface" so we should accept
// uint64 here as well. // uint64 here as well.
if u, ok := sv.(uint64); ok { if u, ok := sv.(uint64); ok {
+2 -2
View File
@@ -13,7 +13,7 @@ type mysqlTx struct {
} }
func (tx *mysqlTx) Commit() (err error) { func (tx *mysqlTx) Commit() (err error) {
if tx.mc == nil || tx.mc.closed.IsSet() { if tx.mc == nil || tx.mc.closed.Load() {
return ErrInvalidConn return ErrInvalidConn
} }
err = tx.mc.exec("COMMIT") err = tx.mc.exec("COMMIT")
@@ -22,7 +22,7 @@ func (tx *mysqlTx) Commit() (err error) {
} }
func (tx *mysqlTx) Rollback() (err error) { func (tx *mysqlTx) Rollback() (err error) {
if tx.mc == nil || tx.mc.closed.IsSet() { if tx.mc == nil || tx.mc.closed.Load() {
return ErrInvalidConn return ErrInvalidConn
} }
err = tx.mc.exec("ROLLBACK") err = tx.mc.exec("ROLLBACK")
+44 -78
View File
@@ -35,26 +35,25 @@ var (
// Note: The provided tls.Config is exclusively owned by the driver after // Note: The provided tls.Config is exclusively owned by the driver after
// registering it. // registering it.
// //
// rootCertPool := x509.NewCertPool() // rootCertPool := x509.NewCertPool()
// pem, err := ioutil.ReadFile("/path/ca-cert.pem") // pem, err := ioutil.ReadFile("/path/ca-cert.pem")
// if err != nil { // if err != nil {
// log.Fatal(err) // log.Fatal(err)
// } // }
// if ok := rootCertPool.AppendCertsFromPEM(pem); !ok { // if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
// log.Fatal("Failed to append PEM.") // log.Fatal("Failed to append PEM.")
// } // }
// clientCert := make([]tls.Certificate, 0, 1) // clientCert := make([]tls.Certificate, 0, 1)
// certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem") // certs, err := tls.LoadX509KeyPair("/path/client-cert.pem", "/path/client-key.pem")
// if err != nil { // if err != nil {
// log.Fatal(err) // log.Fatal(err)
// } // }
// clientCert = append(clientCert, certs) // clientCert = append(clientCert, certs)
// mysql.RegisterTLSConfig("custom", &tls.Config{ // mysql.RegisterTLSConfig("custom", &tls.Config{
// RootCAs: rootCertPool, // RootCAs: rootCertPool,
// Certificates: clientCert, // Certificates: clientCert,
// }) // })
// db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom") // db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom")
//
func RegisterTLSConfig(key string, config *tls.Config) error { func RegisterTLSConfig(key string, config *tls.Config) error {
if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" { if _, isBool := readBool(key); isBool || strings.ToLower(key) == "skip-verify" || strings.ToLower(key) == "preferred" {
return fmt.Errorf("key '%s' is reserved", key) return fmt.Errorf("key '%s' is reserved", key)
@@ -118,10 +117,6 @@ func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {
if err != nil { if err != nil {
return time.Time{}, err return time.Time{}, err
} }
if year <= 0 {
year = 1
}
if b[4] != '-' { if b[4] != '-' {
return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[4]) return time.Time{}, fmt.Errorf("bad value for field: `%c`", b[4])
} }
@@ -130,9 +125,6 @@ func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {
if err != nil { if err != nil {
return time.Time{}, err return time.Time{}, err
} }
if m <= 0 {
m = 1
}
month := time.Month(m) month := time.Month(m)
if b[7] != '-' { if b[7] != '-' {
@@ -143,9 +135,6 @@ func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {
if err != nil { if err != nil {
return time.Time{}, err return time.Time{}, err
} }
if day <= 0 {
day = 1
}
if len(b) == 10 { if len(b) == 10 {
return time.Date(year, month, day, 0, 0, 0, 0, loc), nil return time.Date(year, month, day, 0, 0, 0, 0, loc), nil
} }
@@ -199,7 +188,7 @@ func parseByteYear(b []byte) (int, error) {
return 0, err return 0, err
} }
year += v * n year += v * n
n = n / 10 n /= 10
} }
return year, nil return year, nil
} }
@@ -542,7 +531,7 @@ func stringToInt(b []byte) int {
return val return val
} }
// returns the string read as a bytes slice, wheter the value is NULL, // returns the string read as a bytes slice, whether the value is NULL,
// the number of bytes read and an error, in case the string is longer than // the number of bytes read and an error, in case the string is longer than
// the input slice // the input slice
func readLengthEncodedString(b []byte) ([]byte, bool, int, error) { func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
@@ -652,32 +641,32 @@ func escapeBytesBackslash(buf, v []byte) []byte {
for _, c := range v { for _, c := range v {
switch c { switch c {
case '\x00': case '\x00':
buf[pos] = '\\'
buf[pos+1] = '0' buf[pos+1] = '0'
buf[pos] = '\\'
pos += 2 pos += 2
case '\n': case '\n':
buf[pos] = '\\'
buf[pos+1] = 'n' buf[pos+1] = 'n'
buf[pos] = '\\'
pos += 2 pos += 2
case '\r': case '\r':
buf[pos] = '\\'
buf[pos+1] = 'r' buf[pos+1] = 'r'
buf[pos] = '\\'
pos += 2 pos += 2
case '\x1a': case '\x1a':
buf[pos] = '\\'
buf[pos+1] = 'Z' buf[pos+1] = 'Z'
buf[pos] = '\\'
pos += 2 pos += 2
case '\'': case '\'':
buf[pos] = '\\'
buf[pos+1] = '\'' buf[pos+1] = '\''
buf[pos] = '\\'
pos += 2 pos += 2
case '"': case '"':
buf[pos] = '\\'
buf[pos+1] = '"' buf[pos+1] = '"'
buf[pos] = '\\'
pos += 2 pos += 2
case '\\': case '\\':
buf[pos] = '\\'
buf[pos+1] = '\\' buf[pos+1] = '\\'
buf[pos] = '\\'
pos += 2 pos += 2
default: default:
buf[pos] = c buf[pos] = c
@@ -697,32 +686,32 @@ func escapeStringBackslash(buf []byte, v string) []byte {
c := v[i] c := v[i]
switch c { switch c {
case '\x00': case '\x00':
buf[pos] = '\\'
buf[pos+1] = '0' buf[pos+1] = '0'
buf[pos] = '\\'
pos += 2 pos += 2
case '\n': case '\n':
buf[pos] = '\\'
buf[pos+1] = 'n' buf[pos+1] = 'n'
buf[pos] = '\\'
pos += 2 pos += 2
case '\r': case '\r':
buf[pos] = '\\'
buf[pos+1] = 'r' buf[pos+1] = 'r'
buf[pos] = '\\'
pos += 2 pos += 2
case '\x1a': case '\x1a':
buf[pos] = '\\'
buf[pos+1] = 'Z' buf[pos+1] = 'Z'
buf[pos] = '\\'
pos += 2 pos += 2
case '\'': case '\'':
buf[pos] = '\\'
buf[pos+1] = '\'' buf[pos+1] = '\''
buf[pos] = '\\'
pos += 2 pos += 2
case '"': case '"':
buf[pos] = '\\'
buf[pos+1] = '"' buf[pos+1] = '"'
buf[pos] = '\\'
pos += 2 pos += 2
case '\\': case '\\':
buf[pos] = '\\'
buf[pos+1] = '\\' buf[pos+1] = '\\'
buf[pos] = '\\'
pos += 2 pos += 2
default: default:
buf[pos] = c buf[pos] = c
@@ -744,8 +733,8 @@ func escapeBytesQuotes(buf, v []byte) []byte {
for _, c := range v { for _, c := range v {
if c == '\'' { if c == '\'' {
buf[pos] = '\''
buf[pos+1] = '\'' buf[pos+1] = '\''
buf[pos] = '\''
pos += 2 pos += 2
} else { } else {
buf[pos] = c buf[pos] = c
@@ -764,8 +753,8 @@ func escapeStringQuotes(buf []byte, v string) []byte {
for i := 0; i < len(v); i++ { for i := 0; i < len(v); i++ {
c := v[i] c := v[i]
if c == '\'' { if c == '\'' {
buf[pos] = '\''
buf[pos+1] = '\'' buf[pos+1] = '\''
buf[pos] = '\''
pos += 2 pos += 2
} else { } else {
buf[pos] = c buf[pos] = c
@@ -790,39 +779,16 @@ type noCopy struct{}
// Lock is a no-op used by -copylocks checker from `go vet`. // Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {} func (*noCopy) Lock() {}
// atomicBool is a wrapper around uint32 for usage as a boolean value with // Unlock is a no-op used by -copylocks checker from `go vet`.
// atomic access. // noCopy should implement sync.Locker from Go 1.11
type atomicBool struct { // https://github.com/golang/go/commit/c2eba53e7f80df21d51285879d51ab81bcfbf6bc
_noCopy noCopy // https://github.com/golang/go/issues/26165
value uint32 func (*noCopy) Unlock() {}
}
// IsSet returns whether the current boolean value is true
func (ab *atomicBool) IsSet() bool {
return atomic.LoadUint32(&ab.value) > 0
}
// Set sets the value of the bool regardless of the previous value
func (ab *atomicBool) Set(value bool) {
if value {
atomic.StoreUint32(&ab.value, 1)
} else {
atomic.StoreUint32(&ab.value, 0)
}
}
// TrySet sets the value of the bool and returns whether the value changed
func (ab *atomicBool) TrySet(value bool) bool {
if value {
return atomic.SwapUint32(&ab.value, 1) == 0
}
return atomic.SwapUint32(&ab.value, 0) > 0
}
// atomicError is a wrapper for atomically accessed error values // atomicError is a wrapper for atomically accessed error values
type atomicError struct { type atomicError struct {
_noCopy noCopy _ noCopy
value atomic.Value value atomic.Value
} }
// Set sets the error value regardless of the previous value. // Set sets the error value regardless of the previous value.
+1 -3
View File
@@ -1,6 +1,4 @@
MIT License Copyright (c) 2017 HashiCorp, Inc.
Copyright (c) 2017 HashiCorp
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+3 -2
View File
@@ -140,9 +140,10 @@ log.Printf("[DEBUG] %d", 42)
... [DEBUG] my-app: 42 ... [DEBUG] my-app: 42
``` ```
Notice that if `appLogger` is initialized with the `INFO` log level _and_ you Notice that if `appLogger` is initialized with the `INFO` log level, _and_ you
specify `InferLevels: true`, you will not see any output here. You must change specify `InferLevels: true`, you will not see any output here. You must change
`appLogger` to `DEBUG` to see output. See the docs for more information. `appLogger` to `DEBUG` to see output. See the docs for more information.
If the log lines start with a timestamp you can use the If the log lines start with a timestamp you can use the
`InferLevelsWithTimestamp` option to try and ignore them. `InferLevelsWithTimestamp` option to try and ignore them. Please note that in order
for `InferLevelsWithTimestamp` to be relevant, `InferLevels` must be set to `true`.
+25 -10
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
//go:build !windows //go:build !windows
// +build !windows // +build !windows
@@ -7,23 +10,35 @@ import (
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
) )
// hasFD is used to check if the writer has an Fd value to check
// if it's a terminal.
type hasFD interface {
Fd() uintptr
}
// setColorization will mutate the values of this logger // setColorization will mutate the values of this logger
// to appropriately configure colorization options. It provides // to appropriately configure colorization options. It provides
// a wrapper to the output stream on Windows systems. // a wrapper to the output stream on Windows systems.
func (l *intLogger) setColorization(opts *LoggerOptions) { func (l *intLogger) setColorization(opts *LoggerOptions) {
switch opts.Color { if opts.Color != AutoColor {
case ColorOff:
fallthrough
case ForceColor:
return return
case AutoColor: }
fi := l.checkWriterIsFile()
isUnixTerm := isatty.IsTerminal(fi.Fd()) if sc, ok := l.writer.w.(SupportsColor); ok {
isCygwinTerm := isatty.IsCygwinTerminal(fi.Fd()) if !sc.SupportsColor() {
isTerm := isUnixTerm || isCygwinTerm
if !isTerm {
l.headerColor = ColorOff l.headerColor = ColorOff
l.writer.color = ColorOff l.writer.color = ColorOff
} }
return
}
fi, ok := l.writer.w.(hasFD)
if !ok {
return
}
if !isatty.IsTerminal(fi.Fd()) {
l.headerColor = ColorOff
l.writer.color = ColorOff
} }
} }
+22 -19
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
//go:build windows //go:build windows
// +build windows // +build windows
@@ -7,32 +10,32 @@ import (
"os" "os"
colorable "github.com/mattn/go-colorable" colorable "github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
) )
// setColorization will mutate the values of this logger // setColorization will mutate the values of this logger
// to appropriately configure colorization options. It provides // to appropriately configure colorization options. It provides
// a wrapper to the output stream on Windows systems. // a wrapper to the output stream on Windows systems.
func (l *intLogger) setColorization(opts *LoggerOptions) { func (l *intLogger) setColorization(opts *LoggerOptions) {
switch opts.Color { if opts.Color == ColorOff {
case ColorOff:
return return
case ForceColor: }
fi := l.checkWriterIsFile()
l.writer.w = colorable.NewColorable(fi)
case AutoColor:
fi := l.checkWriterIsFile()
isUnixTerm := isatty.IsTerminal(os.Stdout.Fd())
isCygwinTerm := isatty.IsCygwinTerminal(os.Stdout.Fd())
isTerm := isUnixTerm || isCygwinTerm
if !isTerm {
l.writer.color = ColorOff
l.headerColor = ColorOff
return
}
if l.headerColor == ColorOff { fi, ok := l.writer.w.(*os.File)
l.writer.w = colorable.NewColorable(fi) if !ok {
} l.writer.color = ColorOff
l.headerColor = ColorOff
return
}
cfi := colorable.NewColorable(fi)
// NewColorable detects if color is possible and if it's not, then it
// returns the original value. So we can test if we got the original
// value back to know if color is possible.
if cfi == fi {
l.writer.color = ColorOff
l.headerColor = ColorOff
} else {
l.writer.w = cfi
} }
} }
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog package hclog
import ( import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog package hclog
import ( import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog package hclog
import ( import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog package hclog
import ( import (
+119 -29
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog package hclog
import ( import (
@@ -8,7 +11,6 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"os"
"reflect" "reflect"
"runtime" "runtime"
"sort" "sort"
@@ -53,11 +55,25 @@ var (
faintBoldColor = color.New(color.Faint, color.Bold) faintBoldColor = color.New(color.Faint, color.Bold)
faintColor = color.New(color.Faint) faintColor = color.New(color.Faint)
faintMultiLinePrefix = faintColor.Sprint(" | ") faintMultiLinePrefix string
faintFieldSeparator = faintColor.Sprint("=") faintFieldSeparator string
faintFieldSeparatorWithNewLine = faintColor.Sprint("=\n") faintFieldSeparatorWithNewLine string
) )
func init() {
// Force all the colors to enabled because we do our own detection of color usage.
for _, c := range _levelToColor {
c.EnableColor()
}
faintBoldColor.EnableColor()
faintColor.EnableColor()
faintMultiLinePrefix = faintColor.Sprint(" | ")
faintFieldSeparator = faintColor.Sprint("=")
faintFieldSeparatorWithNewLine = faintColor.Sprint("=\n")
}
// Make sure that intLogger is a Logger // Make sure that intLogger is a Logger
var _ Logger = &intLogger{} var _ Logger = &intLogger{}
@@ -77,6 +93,19 @@ type intLogger struct {
writer *writer writer *writer
level *int32 level *int32
// The value of curEpoch when our level was set
setEpoch uint64
// The value of curEpoch the last time we performed the level sync process
ownEpoch uint64
// Shared amongst all the loggers created in this hierachy, used to determine
// if the level sync process should be run by comparing it with ownEpoch
curEpoch *uint64
// The logger this one was created from. Only set when syncParentLevel is set
parent *intLogger
headerColor ColorOption headerColor ColorOption
fieldColor ColorOption fieldColor ColorOption
@@ -86,6 +115,9 @@ type intLogger struct {
// create subloggers with their own level setting // create subloggers with their own level setting
independentLevels bool independentLevels bool
syncParentLevel bool
subloggerHook func(sub Logger) Logger
} }
// New returns a configured logger. // New returns a configured logger.
@@ -125,9 +157,9 @@ func newLogger(opts *LoggerOptions) *intLogger {
} }
var ( var (
primaryColor ColorOption = ColorOff primaryColor = ColorOff
headerColor ColorOption = ColorOff headerColor = ColorOff
fieldColor ColorOption = ColorOff fieldColor = ColorOff
) )
switch { switch {
case opts.ColorHeaderOnly: case opts.ColorHeaderOnly:
@@ -148,10 +180,13 @@ func newLogger(opts *LoggerOptions) *intLogger {
mutex: mutex, mutex: mutex,
writer: newWriter(output, primaryColor), writer: newWriter(output, primaryColor),
level: new(int32), level: new(int32),
curEpoch: new(uint64),
exclude: opts.Exclude, exclude: opts.Exclude,
independentLevels: opts.IndependentLevels, independentLevels: opts.IndependentLevels,
syncParentLevel: opts.SyncParentLevel,
headerColor: headerColor, headerColor: headerColor,
fieldColor: fieldColor, fieldColor: fieldColor,
subloggerHook: opts.SubloggerHook,
} }
if opts.IncludeLocation { if opts.IncludeLocation {
l.callerOffset = offsetIntLogger + opts.AdditionalLocationOffset l.callerOffset = offsetIntLogger + opts.AdditionalLocationOffset
@@ -167,6 +202,10 @@ func newLogger(opts *LoggerOptions) *intLogger {
l.timeFormat = opts.TimeFormat l.timeFormat = opts.TimeFormat
} }
if l.subloggerHook == nil {
l.subloggerHook = identityHook
}
l.setColorization(opts) l.setColorization(opts)
atomic.StoreInt32(l.level, int32(level)) atomic.StoreInt32(l.level, int32(level))
@@ -174,6 +213,10 @@ func newLogger(opts *LoggerOptions) *intLogger {
return l return l
} }
func identityHook(logger Logger) Logger {
return logger
}
// offsetIntLogger is the stack frame offset in the call stack for the caller to // offsetIntLogger is the stack frame offset in the call stack for the caller to
// one of the Warn, Info, Log, etc methods. // one of the Warn, Info, Log, etc methods.
const offsetIntLogger = 3 const offsetIntLogger = 3
@@ -181,7 +224,7 @@ const offsetIntLogger = 3
// Log a message and a set of key/value pairs if the given level is at // Log a message and a set of key/value pairs if the given level is at
// or more severe that the threshold configured in the Logger. // or more severe that the threshold configured in the Logger.
func (l *intLogger) log(name string, level Level, msg string, args ...interface{}) { func (l *intLogger) log(name string, level Level, msg string, args ...interface{}) {
if level < Level(atomic.LoadInt32(l.level)) { if level < l.GetLevel() {
return return
} }
@@ -261,7 +304,6 @@ func needsQuoting(str string) bool {
// 2. Color the whole log line, based on the level. // 2. Color the whole log line, based on the level.
// 3. Color only the header (level) part of the log line. // 3. Color only the header (level) part of the log line.
// 4. Color both the header and fields of the log line. // 4. Color both the header and fields of the log line.
//
func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, args ...interface{}) { func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, args ...interface{}) {
if !l.disableTime { if !l.disableTime {
@@ -585,7 +627,7 @@ func (l *intLogger) logJSON(t time.Time, name string, level Level, msg string, a
vals := l.jsonMapEntry(t, name, level, msg) vals := l.jsonMapEntry(t, name, level, msg)
args = append(l.implied, args...) args = append(l.implied, args...)
if args != nil && len(args) > 0 { if len(args) > 0 {
if len(args)%2 != 0 { if len(args)%2 != 0 {
cs, ok := args[len(args)-1].(CapturedStacktrace) cs, ok := args[len(args)-1].(CapturedStacktrace)
if ok { if ok {
@@ -706,27 +748,27 @@ func (l *intLogger) Error(msg string, args ...interface{}) {
// Indicate that the logger would emit TRACE level logs // Indicate that the logger would emit TRACE level logs
func (l *intLogger) IsTrace() bool { func (l *intLogger) IsTrace() bool {
return Level(atomic.LoadInt32(l.level)) == Trace return l.GetLevel() == Trace
} }
// Indicate that the logger would emit DEBUG level logs // Indicate that the logger would emit DEBUG level logs
func (l *intLogger) IsDebug() bool { func (l *intLogger) IsDebug() bool {
return Level(atomic.LoadInt32(l.level)) <= Debug return l.GetLevel() <= Debug
} }
// Indicate that the logger would emit INFO level logs // Indicate that the logger would emit INFO level logs
func (l *intLogger) IsInfo() bool { func (l *intLogger) IsInfo() bool {
return Level(atomic.LoadInt32(l.level)) <= Info return l.GetLevel() <= Info
} }
// Indicate that the logger would emit WARN level logs // Indicate that the logger would emit WARN level logs
func (l *intLogger) IsWarn() bool { func (l *intLogger) IsWarn() bool {
return Level(atomic.LoadInt32(l.level)) <= Warn return l.GetLevel() <= Warn
} }
// Indicate that the logger would emit ERROR level logs // Indicate that the logger would emit ERROR level logs
func (l *intLogger) IsError() bool { func (l *intLogger) IsError() bool {
return Level(atomic.LoadInt32(l.level)) <= Error return l.GetLevel() <= Error
} }
const MissingKey = "EXTRA_VALUE_AT_END" const MissingKey = "EXTRA_VALUE_AT_END"
@@ -776,7 +818,7 @@ func (l *intLogger) With(args ...interface{}) Logger {
sl.implied = append(sl.implied, MissingKey, extra) sl.implied = append(sl.implied, MissingKey, extra)
} }
return sl return l.subloggerHook(sl)
} }
// Create a new sub-Logger that a name decending from the current name. // Create a new sub-Logger that a name decending from the current name.
@@ -790,7 +832,7 @@ func (l *intLogger) Named(name string) Logger {
sl.name = name sl.name = name
} }
return sl return l.subloggerHook(sl)
} }
// Create a new sub-Logger with an explicit name. This ignores the current // Create a new sub-Logger with an explicit name. This ignores the current
@@ -801,7 +843,7 @@ func (l *intLogger) ResetNamed(name string) Logger {
sl.name = name sl.name = name
return sl return l.subloggerHook(sl)
} }
func (l *intLogger) ResetOutput(opts *LoggerOptions) error { func (l *intLogger) ResetOutput(opts *LoggerOptions) error {
@@ -842,7 +884,63 @@ func (l *intLogger) resetOutput(opts *LoggerOptions) error {
// Update the logging level on-the-fly. This will affect all subloggers as // Update the logging level on-the-fly. This will affect all subloggers as
// well. // well.
func (l *intLogger) SetLevel(level Level) { func (l *intLogger) SetLevel(level Level) {
atomic.StoreInt32(l.level, int32(level)) if !l.syncParentLevel {
atomic.StoreInt32(l.level, int32(level))
return
}
nsl := new(int32)
*nsl = int32(level)
l.level = nsl
l.ownEpoch = atomic.AddUint64(l.curEpoch, 1)
l.setEpoch = l.ownEpoch
}
func (l *intLogger) searchLevelPtr() *int32 {
p := l.parent
ptr := l.level
max := l.setEpoch
for p != nil {
if p.setEpoch > max {
max = p.setEpoch
ptr = p.level
}
p = p.parent
}
return ptr
}
// Returns the current level
func (l *intLogger) GetLevel() Level {
// We perform the loads immediately to keep the CPU pipeline busy, which
// effectively makes the second load cost nothing. Once loaded into registers
// the comparison returns the already loaded value. The comparison is almost
// always true, so the branch predictor should hit consistently with it.
var (
curEpoch = atomic.LoadUint64(l.curEpoch)
level = Level(atomic.LoadInt32(l.level))
own = l.ownEpoch
)
if curEpoch == own {
return level
}
// Perform the level sync process. We'll avoid doing this next time by seeing the
// epoch as current.
ptr := l.searchLevelPtr()
l.level = ptr
l.ownEpoch = curEpoch
return Level(atomic.LoadInt32(ptr))
} }
// Create a *log.Logger that will send it's data through this Logger. This // Create a *log.Logger that will send it's data through this Logger. This
@@ -872,16 +970,6 @@ func (l *intLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer {
} }
} }
// checks if the underlying io.Writer is a file, and
// panics if not. For use by colorization.
func (l *intLogger) checkWriterIsFile() *os.File {
fi, ok := l.writer.w.(*os.File)
if !ok {
panic("Cannot enable coloring of non-file Writers")
}
return fi
}
// Accept implements the SinkAdapter interface // Accept implements the SinkAdapter interface
func (i *intLogger) Accept(name string, level Level, msg string, args ...interface{}) { func (i *intLogger) Accept(name string, level Level, msg string, args ...interface{}) {
i.log(name, level, msg, args...) i.log(name, level, msg, args...)
@@ -905,6 +993,8 @@ func (l *intLogger) copy() *intLogger {
if l.independentLevels { if l.independentLevels {
sl.level = new(int32) sl.level = new(int32)
*sl.level = *l.level *sl.level = *l.level
} else if l.syncParentLevel {
sl.parent = l
} }
return &sl return &sl
+39
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog package hclog
import ( import (
@@ -89,6 +92,13 @@ const (
ForceColor ForceColor
) )
// SupportsColor is an optional interface that can be implemented by the output
// value. If implemented and SupportsColor() returns true, then AutoColor will
// enable colorization.
type SupportsColor interface {
SupportsColor() bool
}
// LevelFromString returns a Level type for the named log level, or "NoLevel" if // LevelFromString returns a Level type for the named log level, or "NoLevel" if
// the level string is invalid. This facilitates setting the log level via // the level string is invalid. This facilitates setting the log level via
// config or environment variable by name in a predictable way. // config or environment variable by name in a predictable way.
@@ -198,6 +208,9 @@ type Logger interface {
// implementation cannot update the level on the fly, it should no-op. // implementation cannot update the level on the fly, it should no-op.
SetLevel(level Level) SetLevel(level Level)
// Returns the current level
GetLevel() Level
// Return a value that conforms to the stdlib log.Logger interface // Return a value that conforms to the stdlib log.Logger interface
StandardLogger(opts *StandardLoggerOptions) *log.Logger StandardLogger(opts *StandardLoggerOptions) *log.Logger
@@ -220,6 +233,7 @@ type StandardLoggerOptions struct {
// [DEBUG] and strip it off before reapplying it. // [DEBUG] and strip it off before reapplying it.
// The timestamp detection may result in false positives and incomplete // The timestamp detection may result in false positives and incomplete
// string outputs. // string outputs.
// InferLevelsWithTimestamp is only relevant if InferLevels is true.
InferLevelsWithTimestamp bool InferLevelsWithTimestamp bool
// ForceLevel is used to force all output from the standard logger to be at // ForceLevel is used to force all output from the standard logger to be at
@@ -289,6 +303,31 @@ type LoggerOptions struct {
// logger will not affect any subloggers, and SetLevel on any subloggers // logger will not affect any subloggers, and SetLevel on any subloggers
// will not affect the parent or sibling loggers. // will not affect the parent or sibling loggers.
IndependentLevels bool IndependentLevels bool
// When set, changing the level of a logger effects only it's direct sub-loggers
// rather than all sub-loggers. For example:
// a := logger.Named("a")
// a.SetLevel(Error)
// b := a.Named("b")
// c := a.Named("c")
// b.GetLevel() => Error
// c.GetLevel() => Error
// b.SetLevel(Info)
// a.GetLevel() => Error
// b.GetLevel() => Info
// c.GetLevel() => Error
// a.SetLevel(Warn)
// a.GetLevel() => Warn
// b.GetLevel() => Warn
// c.GetLevel() => Warn
SyncParentLevel bool
// SubloggerHook registers a function that is called when a sublogger via
// Named, With, or ResetNamed is created. If defined, the function is passed
// the newly created Logger and the returned Logger is returned from the
// original function. This option allows customization via interception and
// wrapping of Logger instances.
SubloggerHook func(sub Logger) Logger
} }
// InterceptLogger describes the interface for using a logger // InterceptLogger describes the interface for using a logger
+5
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog package hclog
import ( import (
@@ -49,6 +52,8 @@ func (l *nullLogger) ResetNamed(name string) Logger { return l }
func (l *nullLogger) SetLevel(level Level) {} func (l *nullLogger) SetLevel(level Level) {}
func (l *nullLogger) GetLevel() Level { return NoLevel }
func (l *nullLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger { func (l *nullLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
return log.New(l.StandardWriter(opts), "", log.LstdFlags) return log.New(l.StandardWriter(opts), "", log.LstdFlags)
} }
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog package hclog
import ( import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MIT
package hclog package hclog
import ( import (
+85 -2
View File
@@ -1,3 +1,88 @@
## v1.6.0
CHANGES:
* plugin: Plugins written in other languages can optionally start to advertise whether they support gRPC broker multiplexing.
If the environment variable `PLUGIN_MULTIPLEX_GRPC` is set, it is safe to include a seventh field containing a boolean
value in the `|`-separated protocol negotiation line.
ENHANCEMENTS:
* Support muxing gRPC broker connections over a single listener [[GH-288](https://github.com/hashicorp/go-plugin/pull/288)]
* client: Configurable buffer size for reading plugin log lines [[GH-265](https://github.com/hashicorp/go-plugin/pull/265)]
* Use `buf` for proto generation [[GH-286](https://github.com/hashicorp/go-plugin/pull/286)]
* deps: bump golang.org/x/net to v0.17.0 [[GH-285](https://github.com/hashicorp/go-plugin/pull/285)]
* deps: bump golang.org/x/sys to v0.13.0 [[GH-285](https://github.com/hashicorp/go-plugin/pull/285)]
* deps: bump golang.org/x/text to v0.13.0 [[GH-285](https://github.com/hashicorp/go-plugin/pull/285)]
## v1.5.2
ENHANCEMENTS:
client: New `UnixSocketConfig.TempDir` option allows setting the directory to use when creating plugin-specific Unix socket directories [[GH-282](https://github.com/hashicorp/go-plugin/pull/282)]
## v1.5.1
BUGS:
* server: `PLUGIN_UNIX_SOCKET_DIR` is consistently used for gRPC broker sockets as well as the initial socket [[GH-277](https://github.com/hashicorp/go-plugin/pull/277)]
ENHANCEMENTS:
* client: New `UnixSocketConfig` option in `ClientConfig` to support making the client's Unix sockets group-writable [[GH-277](https://github.com/hashicorp/go-plugin/pull/277)]
## v1.5.0
ENHANCEMENTS:
* client: New `runner.Runner` interface to support clients providing custom plugin command runner implementations [[GH-270](https://github.com/hashicorp/go-plugin/pull/270)]
* Accessible via new `ClientConfig` field `RunnerFunc`, which is mutually exclusive with `Cmd` and `Reattach`
* Reattaching support via `ReattachConfig` field `ReattachFunc`
* client: New `ClientConfig` field `SkipHostEnv` allows omitting the client process' own environment variables from the plugin command's environment [[GH-270](https://github.com/hashicorp/go-plugin/pull/270)]
* client: Add `ID()` method to `Client` for retrieving the pid or other unique ID of a running plugin [[GH-272](https://github.com/hashicorp/go-plugin/pull/272)]
* server: Support setting the directory to create Unix sockets in with the env var `PLUGIN_UNIX_SOCKET_DIR` [[GH-270](https://github.com/hashicorp/go-plugin/pull/270)]
* server: Support setting group write permission and a custom group name or gid owner with the env var `PLUGIN_UNIX_SOCKET_GROUP` [[GH-270](https://github.com/hashicorp/go-plugin/pull/270)]
## v1.4.11-rc1
ENHANCEMENTS:
* deps: bump protoreflect to v1.15.1 [[GH-264](https://github.com/hashicorp/go-plugin/pull/264)]
## v1.4.10
BUG FIXES:
* additional notes: ensure to close files [[GH-241](https://github.com/hashicorp/go-plugin/pull/241)]
ENHANCEMENTS:
* deps: Remove direct dependency on golang.org/x/net [[GH-240](https://github.com/hashicorp/go-plugin/pull/240)]
## v1.4.9
ENHANCEMENTS:
* client: Remove log warning introduced in 1.4.5 when SecureConfig is nil. [[GH-238](https://github.com/hashicorp/go-plugin/pull/238)]
## v1.4.8
BUG FIXES:
* Fix windows build: [[GH-227](https://github.com/hashicorp/go-plugin/pull/227)]
## v1.4.7
ENHANCEMENTS:
* More detailed error message on plugin start failure: [[GH-223](https://github.com/hashicorp/go-plugin/pull/223)]
## v1.4.6
BUG FIXES:
* server: Prevent gRPC broker goroutine leak when using `GRPCServer` type `GracefulStop()` or `Stop()` methods [[GH-220](https://github.com/hashicorp/go-plugin/pull/220)]
## v1.4.5 ## v1.4.5
ENHANCEMENTS: ENHANCEMENTS:
@@ -15,5 +100,3 @@ BUG FIXES:
* Bidirectional communication: fix bidirectional communication when AutoMTLS is enabled [[GH-193](https://github.com/hashicorp/go-plugin/pull/193)] * Bidirectional communication: fix bidirectional communication when AutoMTLS is enabled [[GH-193](https://github.com/hashicorp/go-plugin/pull/193)]
* RPC: Trim a spurious log message for plugins using RPC [[GH-186](https://github.com/hashicorp/go-plugin/pull/186)] * RPC: Trim a spurious log message for plugins using RPC [[GH-186](https://github.com/hashicorp/go-plugin/pull/186)]
+2
View File
@@ -1,3 +1,5 @@
Copyright (c) 2016 HashiCorp, Inc.
Mozilla Public License, version 2.0 Mozilla Public License, version 2.0
1. Definitions 1. Definitions
+3 -2
View File
@@ -4,8 +4,9 @@
that has been in use by HashiCorp tooling for over 4 years. While initially that has been in use by HashiCorp tooling for over 4 years. While initially
created for [Packer](https://www.packer.io), it is additionally in use by created for [Packer](https://www.packer.io), it is additionally in use by
[Terraform](https://www.terraform.io), [Nomad](https://www.nomadproject.io), [Terraform](https://www.terraform.io), [Nomad](https://www.nomadproject.io),
[Vault](https://www.vaultproject.io), and [Vault](https://www.vaultproject.io),
[Boundary](https://www.boundaryproject.io). [Boundary](https://www.boundaryproject.io),
and [Waypoint](https://www.waypointproject.io).
While the plugin system is over RPC, it is currently only designed to work While the plugin system is over RPC, it is currently only designed to work
over a local [reliable] network. Plugins over a real network are not supported over a local [reliable] network. Plugins over a real network are not supported
+14
View File
@@ -0,0 +1,14 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
version: v1
plugins:
- plugin: buf.build/protocolbuffers/go
out: .
opt:
- paths=source_relative
- plugin: buf.build/grpc/go:v1.3.0
out: .
opt:
- paths=source_relative
- require_unimplemented_servers=false
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
version: v1
build:
excludes:
- examples/
+287 -103
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin package plugin
import ( import (
@@ -23,6 +26,9 @@ import (
"time" "time"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin/internal/cmdrunner"
"github.com/hashicorp/go-plugin/internal/grpcmux"
"github.com/hashicorp/go-plugin/runner"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@@ -41,7 +47,7 @@ var managedClientsLock sync.Mutex
var ( var (
// ErrProcessNotFound is returned when a client is instantiated to // ErrProcessNotFound is returned when a client is instantiated to
// reattach to an existing process and it isn't found. // reattach to an existing process and it isn't found.
ErrProcessNotFound = errors.New("Reattachment process not found") ErrProcessNotFound = cmdrunner.ErrProcessNotFound
// ErrChecksumsDoNotMatch is returned when binary's checksum doesn't match // ErrChecksumsDoNotMatch is returned when binary's checksum doesn't match
// the one provided in the SecureConfig. // the one provided in the SecureConfig.
@@ -58,8 +64,18 @@ var (
// ErrSecureConfigAndReattach is returned when both Reattach and // ErrSecureConfigAndReattach is returned when both Reattach and
// SecureConfig are set. // SecureConfig are set.
ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set") ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set")
// ErrGRPCBrokerMuxNotSupported is returned when the client requests
// multiplexing over the gRPC broker, but the plugin does not support the
// feature. In most cases, this should be resolvable by updating and
// rebuilding the plugin, or restarting the plugin with
// ClientConfig.GRPCBrokerMultiplex set to false.
ErrGRPCBrokerMuxNotSupported = errors.New("client requested gRPC broker multiplexing but plugin does not support the feature")
) )
// defaultPluginLogBufferSize is the default size of the buffer used to read from stderr for plugin log lines.
const defaultPluginLogBufferSize = 64 * 1024
// Client handles the lifecycle of a plugin application. It launches // Client handles the lifecycle of a plugin application. It launches
// plugins, connects to them, dispenses interface implementations, and handles // plugins, connects to them, dispenses interface implementations, and handles
// killing the process. // killing the process.
@@ -76,7 +92,7 @@ type Client struct {
exited bool exited bool
l sync.Mutex l sync.Mutex
address net.Addr address net.Addr
process *os.Process runner runner.AttachedRunner
client ClientProtocol client ClientProtocol
protocol Protocol protocol Protocol
logger hclog.Logger logger hclog.Logger
@@ -95,6 +111,11 @@ type Client struct {
// processKilled is used for testing only, to flag when the process was // processKilled is used for testing only, to flag when the process was
// forcefully killed. // forcefully killed.
processKilled bool processKilled bool
unixSocketCfg UnixSocketConfig
grpcMuxerOnce sync.Once
grpcMuxer *grpcmux.GRPCClientMuxer
} }
// NegotiatedVersion returns the protocol version negotiated with the server. // NegotiatedVersion returns the protocol version negotiated with the server.
@@ -103,6 +124,19 @@ func (c *Client) NegotiatedVersion() int {
return c.negotiatedVersion return c.negotiatedVersion
} }
// ID returns a unique ID for the running plugin. By default this is the process
// ID (pid), but it could take other forms if RunnerFunc was provided.
func (c *Client) ID() string {
c.l.Lock()
defer c.l.Unlock()
if c.runner != nil {
return c.runner.ID()
}
return ""
}
// ClientConfig is the configuration used to initialize a new // ClientConfig is the configuration used to initialize a new
// plugin client. After being used to initialize a plugin client, // plugin client. After being used to initialize a plugin client,
// that configuration must not be modified again. // that configuration must not be modified again.
@@ -130,6 +164,13 @@ type ClientConfig struct {
Cmd *exec.Cmd Cmd *exec.Cmd
Reattach *ReattachConfig Reattach *ReattachConfig
// RunnerFunc allows consumers to provide their own implementation of
// runner.Runner and control the context within which a plugin is executed.
// The cmd argument will have been copied from the config and populated with
// environment variables that a go-plugin server expects to read such as
// AutoMTLS certs and the magic cookie key.
RunnerFunc func(l hclog.Logger, cmd *exec.Cmd, tmpDir string) (runner.Runner, error)
// SecureConfig is configuration for verifying the integrity of the // SecureConfig is configuration for verifying the integrity of the
// executable. It can not be used with Reattach. // executable. It can not be used with Reattach.
SecureConfig *SecureConfig SecureConfig *SecureConfig
@@ -182,6 +223,10 @@ type ClientConfig struct {
// it will default to hclog's default logger. // it will default to hclog's default logger.
Logger hclog.Logger Logger hclog.Logger
// PluginLogBufferSize is the buffer size(bytes) to read from stderr for plugin log lines.
// If this is 0, then the default of 64KB is used.
PluginLogBufferSize int
// AutoMTLS has the client and server automatically negotiate mTLS for // AutoMTLS has the client and server automatically negotiate mTLS for
// transport authentication. This ensures that only the original client will // transport authentication. This ensures that only the original client will
// be allowed to connect to the server, and all other connections will be // be allowed to connect to the server, and all other connections will be
@@ -209,6 +254,44 @@ type ClientConfig struct {
// to create gRPC connections. This only affects plugins using the gRPC // to create gRPC connections. This only affects plugins using the gRPC
// protocol. // protocol.
GRPCDialOptions []grpc.DialOption GRPCDialOptions []grpc.DialOption
// GRPCBrokerMultiplex turns on multiplexing for the gRPC broker. The gRPC
// broker will multiplex all brokered gRPC servers over the plugin's original
// listener socket instead of making a new listener for each server. The
// go-plugin library currently only includes a Go implementation for the
// server (i.e. plugin) side of gRPC broker multiplexing.
//
// Does not support reattaching.
//
// Multiplexed gRPC streams MUST be established sequentially, i.e. after
// calling AcceptAndServe from one side, wait for the other side to Dial
// before calling AcceptAndServe again.
GRPCBrokerMultiplex bool
// SkipHostEnv allows plugins to run without inheriting the parent process'
// environment variables.
SkipHostEnv bool
// UnixSocketConfig configures additional options for any Unix sockets
// that are created. Not normally required. Not supported on Windows.
UnixSocketConfig *UnixSocketConfig
}
type UnixSocketConfig struct {
// If set, go-plugin will change the owner of any Unix sockets created to
// this group, and set them as group-writable. Can be a name or gid. The
// client process must be a member of this group or chown will fail.
Group string
// TempDir specifies the base directory to use when creating a plugin-specific
// temporary directory. It is expected to already exist and be writable. If
// not set, defaults to the directory chosen by os.MkdirTemp.
TempDir string
// The directory to create Unix sockets in. Internally created and managed
// by go-plugin and deleted when the plugin is killed. Will be created
// inside TempDir if specified.
socketDir string
} }
// ReattachConfig is used to configure a client to reattach to an // ReattachConfig is used to configure a client to reattach to an
@@ -220,6 +303,11 @@ type ReattachConfig struct {
Addr net.Addr Addr net.Addr
Pid int Pid int
// ReattachFunc allows consumers to provide their own implementation of
// runner.AttachedRunner and attach to something other than a plain process.
// At least one of Pid or ReattachFunc must be set.
ReattachFunc runner.ReattachFunc
// Test is set to true if this is reattaching to to a plugin in "test mode" // Test is set to true if this is reattaching to to a plugin in "test mode"
// (see ServeConfig.Test). In this mode, client.Kill will NOT kill the // (see ServeConfig.Test). In this mode, client.Kill will NOT kill the
// process and instead will rely on the plugin to terminate itself. This // process and instead will rely on the plugin to terminate itself. This
@@ -295,11 +383,11 @@ func CleanupClients() {
wg.Wait() wg.Wait()
} }
// Creates a new plugin client which manages the lifecycle of an external // NewClient creates a new plugin client which manages the lifecycle of an external
// plugin and gets the address for the RPC connection. // plugin and gets the address for the RPC connection.
// //
// The client must be cleaned up at some point by calling Kill(). If // The client must be cleaned up at some point by calling Kill(). If
// the client is a managed client (created with NewManagedClient) you // the client is a managed client (created with ClientConfig.Managed) you
// can just call CleanupClients at the end of your program and they will // can just call CleanupClients at the end of your program and they will
// be properly cleaned. // be properly cleaned.
func NewClient(config *ClientConfig) (c *Client) { func NewClient(config *ClientConfig) (c *Client) {
@@ -317,10 +405,10 @@ func NewClient(config *ClientConfig) (c *Client) {
} }
if config.SyncStdout == nil { if config.SyncStdout == nil {
config.SyncStdout = ioutil.Discard config.SyncStdout = io.Discard
} }
if config.SyncStderr == nil { if config.SyncStderr == nil {
config.SyncStderr = ioutil.Discard config.SyncStderr = io.Discard
} }
if config.AllowedProtocols == nil { if config.AllowedProtocols == nil {
@@ -335,6 +423,10 @@ func NewClient(config *ClientConfig) (c *Client) {
}) })
} }
if config.PluginLogBufferSize == 0 {
config.PluginLogBufferSize = defaultPluginLogBufferSize
}
c = &Client{ c = &Client{
config: config, config: config,
logger: config.Logger, logger: config.Logger,
@@ -407,12 +499,13 @@ func (c *Client) killed() bool {
func (c *Client) Kill() { func (c *Client) Kill() {
// Grab a lock to read some private fields. // Grab a lock to read some private fields.
c.l.Lock() c.l.Lock()
process := c.process runner := c.runner
addr := c.address addr := c.address
hostSocketDir := c.unixSocketCfg.socketDir
c.l.Unlock() c.l.Unlock()
// If there is no process, there is nothing to kill. // If there is no runner or ID, there is nothing to kill.
if process == nil { if runner == nil || runner.ID() == "" {
return return
} }
@@ -420,10 +513,14 @@ func (c *Client) Kill() {
// Wait for the all client goroutines to finish. // Wait for the all client goroutines to finish.
c.clientWaitGroup.Wait() c.clientWaitGroup.Wait()
if hostSocketDir != "" {
os.RemoveAll(hostSocketDir)
}
// Make sure there is no reference to the old process after it has been // Make sure there is no reference to the old process after it has been
// killed. // killed.
c.l.Lock() c.l.Lock()
c.process = nil c.runner = nil
c.l.Unlock() c.l.Unlock()
}() }()
@@ -466,14 +563,16 @@ func (c *Client) Kill() {
// If graceful exiting failed, just kill it // If graceful exiting failed, just kill it
c.logger.Warn("plugin failed to exit gracefully") c.logger.Warn("plugin failed to exit gracefully")
process.Kill() if err := runner.Kill(context.Background()); err != nil {
c.logger.Debug("error killing plugin", "error", err)
}
c.l.Lock() c.l.Lock()
c.processKilled = true c.processKilled = true
c.l.Unlock() c.l.Unlock()
} }
// Starts the underlying subprocess, communicating with it to negotiate // Start the underlying subprocess, communicating with it to negotiate
// a port for RPC connections, and returning the address to connect via RPC. // a port for RPC connections, and returning the address to connect via RPC.
// //
// This method is safe to call multiple times. Subsequent calls have no effect. // This method is safe to call multiple times. Subsequent calls have no effect.
@@ -491,16 +590,27 @@ func (c *Client) Start() (addr net.Addr, err error) {
// this in a {} for scoping reasons, and hopeful that the escape // this in a {} for scoping reasons, and hopeful that the escape
// analysis will pop the stack here. // analysis will pop the stack here.
{ {
cmdSet := c.config.Cmd != nil var mutuallyExclusiveOptions int
attachSet := c.config.Reattach != nil if c.config.Cmd != nil {
secureSet := c.config.SecureConfig != nil mutuallyExclusiveOptions += 1
if cmdSet == attachSet { }
return nil, fmt.Errorf("Only one of Cmd or Reattach must be set") if c.config.Reattach != nil {
mutuallyExclusiveOptions += 1
}
if c.config.RunnerFunc != nil {
mutuallyExclusiveOptions += 1
}
if mutuallyExclusiveOptions != 1 {
return nil, fmt.Errorf("exactly one of Cmd, or Reattach, or RunnerFunc must be set")
} }
if secureSet && attachSet { if c.config.SecureConfig != nil && c.config.Reattach != nil {
return nil, ErrSecureConfigAndReattach return nil, ErrSecureConfigAndReattach
} }
if c.config.GRPCBrokerMultiplex && c.config.Reattach != nil {
return nil, fmt.Errorf("gRPC broker multiplexing is not supported with Reattach config")
}
} }
if c.config.Reattach != nil { if c.config.Reattach != nil {
@@ -532,24 +642,24 @@ func (c *Client) Start() (addr net.Addr, err error) {
fmt.Sprintf("PLUGIN_MAX_PORT=%d", c.config.MaxPort), fmt.Sprintf("PLUGIN_MAX_PORT=%d", c.config.MaxPort),
fmt.Sprintf("PLUGIN_PROTOCOL_VERSIONS=%s", strings.Join(versionStrings, ",")), fmt.Sprintf("PLUGIN_PROTOCOL_VERSIONS=%s", strings.Join(versionStrings, ",")),
} }
if c.config.GRPCBrokerMultiplex {
env = append(env, fmt.Sprintf("%s=true", envMultiplexGRPC))
}
cmd := c.config.Cmd cmd := c.config.Cmd
cmd.Env = append(cmd.Env, os.Environ()...) if cmd == nil {
// It's only possible to get here if RunnerFunc is non-nil, but we'll
// still use cmd as a spec to populate metadata for the external
// implementation to consume.
cmd = exec.Command("")
}
if !c.config.SkipHostEnv {
cmd.Env = append(cmd.Env, os.Environ()...)
}
cmd.Env = append(cmd.Env, env...) cmd.Env = append(cmd.Env, env...)
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmdStdout, err := cmd.StdoutPipe() if c.config.SecureConfig != nil {
if err != nil {
return nil, err
}
cmdStderr, err := cmd.StderrPipe()
if err != nil {
return nil, err
}
if c.config.SecureConfig == nil {
c.logger.Warn("plugin configured with a nil SecureConfig")
} else {
if ok, err := c.config.SecureConfig.Check(cmd.Path); err != nil { if ok, err := c.config.SecureConfig.Check(cmd.Path); err != nil {
return nil, fmt.Errorf("error verifying checksum: %s", err) return nil, fmt.Errorf("error verifying checksum: %s", err)
} else if !ok { } else if !ok {
@@ -582,26 +692,62 @@ func (c *Client) Start() (addr net.Addr, err error) {
} }
} }
c.logger.Debug("starting plugin", "path", cmd.Path, "args", cmd.Args) if c.config.UnixSocketConfig != nil {
err = cmd.Start() c.unixSocketCfg = *c.config.UnixSocketConfig
if err != nil {
return
} }
// Set the process if c.unixSocketCfg.Group != "" {
c.process = cmd.Process cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvUnixSocketGroup, c.unixSocketCfg.Group))
c.logger.Debug("plugin started", "path", cmd.Path, "pid", c.process.Pid) }
var runner runner.Runner
switch {
case c.config.RunnerFunc != nil:
c.unixSocketCfg.socketDir, err = os.MkdirTemp(c.unixSocketCfg.TempDir, "plugin-dir")
if err != nil {
return nil, err
}
// os.MkdirTemp creates folders with 0o700, so if we have a group
// configured we need to make it group-writable.
if c.unixSocketCfg.Group != "" {
err = setGroupWritable(c.unixSocketCfg.socketDir, c.unixSocketCfg.Group, 0o770)
if err != nil {
return nil, err
}
}
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", EnvUnixSocketDir, c.unixSocketCfg.socketDir))
c.logger.Trace("created temporary directory for unix sockets", "dir", c.unixSocketCfg.socketDir)
runner, err = c.config.RunnerFunc(c.logger, cmd, c.unixSocketCfg.socketDir)
if err != nil {
return nil, err
}
default:
runner, err = cmdrunner.NewCmdRunner(c.logger, cmd)
if err != nil {
return nil, err
}
}
c.runner = runner
startCtx, startCtxCancel := context.WithTimeout(context.Background(), c.config.StartTimeout)
defer startCtxCancel()
err = runner.Start(startCtx)
if err != nil {
return nil, err
}
// Make sure the command is properly cleaned up if there is an error // Make sure the command is properly cleaned up if there is an error
defer func() { defer func() {
r := recover() rErr := recover()
if err != nil || r != nil { if err != nil || rErr != nil {
cmd.Process.Kill() runner.Kill(context.Background())
} }
if r != nil { if rErr != nil {
panic(r) panic(rErr)
} }
}() }()
@@ -612,7 +758,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
c.clientWaitGroup.Add(1) c.clientWaitGroup.Add(1)
c.stderrWaitGroup.Add(1) c.stderrWaitGroup.Add(1)
// logStderr calls Done() // logStderr calls Done()
go c.logStderr(cmdStderr) go c.logStderr(runner.Name(), runner.Stderr())
c.clientWaitGroup.Add(1) c.clientWaitGroup.Add(1)
go func() { go func() {
@@ -621,29 +767,17 @@ func (c *Client) Start() (addr net.Addr, err error) {
defer c.clientWaitGroup.Done() defer c.clientWaitGroup.Done()
// get the cmd info early, since the process information will be removed
// in Kill.
pid := c.process.Pid
path := cmd.Path
// wait to finish reading from stderr since the stderr pipe reader // wait to finish reading from stderr since the stderr pipe reader
// will be closed by the subsequent call to cmd.Wait(). // will be closed by the subsequent call to cmd.Wait().
c.stderrWaitGroup.Wait() c.stderrWaitGroup.Wait()
// Wait for the command to end. // Wait for the command to end.
err := cmd.Wait() err := runner.Wait(context.Background())
msgArgs := []interface{}{
"path", path,
"pid", pid,
}
if err != nil { if err != nil {
msgArgs = append(msgArgs, c.logger.Error("plugin process exited", "plugin", runner.Name(), "id", runner.ID(), "error", err.Error())
[]interface{}{"error", err.Error()}...)
c.logger.Error("plugin process exited", msgArgs...)
} else { } else {
// Log and make sure to flush the logs right away // Log and make sure to flush the logs right away
c.logger.Info("plugin process exited", msgArgs...) c.logger.Info("plugin process exited", "plugin", runner.Name(), "id", runner.ID())
} }
os.Stderr.Sync() os.Stderr.Sync()
@@ -662,10 +796,13 @@ func (c *Client) Start() (addr net.Addr, err error) {
defer c.clientWaitGroup.Done() defer c.clientWaitGroup.Done()
defer close(linesCh) defer close(linesCh)
scanner := bufio.NewScanner(cmdStdout) scanner := bufio.NewScanner(runner.Stdout())
for scanner.Scan() { for scanner.Scan() {
linesCh <- scanner.Text() linesCh <- scanner.Text()
} }
if scanner.Err() != nil {
c.logger.Error("error encountered while scanning stdout", "error", scanner.Err())
}
}() }()
// Make sure after we exit we read the lines from stdout forever // Make sure after we exit we read the lines from stdout forever
@@ -685,22 +822,27 @@ func (c *Client) Start() (addr net.Addr, err error) {
timeout := time.After(c.config.StartTimeout) timeout := time.After(c.config.StartTimeout)
// Start looking for the address // Start looking for the address
c.logger.Debug("waiting for RPC address", "path", cmd.Path) c.logger.Debug("waiting for RPC address", "plugin", runner.Name())
select { select {
case <-timeout: case <-timeout:
err = errors.New("timeout while waiting for plugin to start") err = errors.New("timeout while waiting for plugin to start")
case <-c.doneCtx.Done(): case <-c.doneCtx.Done():
err = errors.New("plugin exited before we could connect") err = errors.New("plugin exited before we could connect")
case line := <-linesCh: case line, ok := <-linesCh:
// Trim the line and split by "|" in order to get the parts of // Trim the line and split by "|" in order to get the parts of
// the output. // the output.
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
parts := strings.SplitN(line, "|", 6) parts := strings.Split(line, "|")
if len(parts) < 4 { if len(parts) < 4 {
err = fmt.Errorf( errText := fmt.Sprintf("Unrecognized remote plugin message: %s", line)
"Unrecognized remote plugin message: %s\n\n"+ if !ok {
"This usually means that the plugin is either invalid or simply\n"+ errText += "\n" + "Failed to read any lines from plugin's stdout"
"needs to be recompiled to support the latest protocol.", line) }
additionalNotes := runner.Diagnose(context.Background())
if additionalNotes != "" {
errText += "\n" + additionalNotes
}
err = errors.New(errText)
return return
} }
@@ -735,13 +877,18 @@ func (c *Client) Start() (addr net.Addr, err error) {
c.negotiatedVersion = version c.negotiatedVersion = version
c.logger.Debug("using plugin", "version", version) c.logger.Debug("using plugin", "version", version)
switch parts[2] { network, address, err := runner.PluginToHost(parts[2], parts[3])
if err != nil {
return addr, err
}
switch network {
case "tcp": case "tcp":
addr, err = net.ResolveTCPAddr("tcp", parts[3]) addr, err = net.ResolveTCPAddr("tcp", address)
case "unix": case "unix":
addr, err = net.ResolveUnixAddr("unix", parts[3]) addr, err = net.ResolveUnixAddr("unix", address)
default: default:
err = fmt.Errorf("Unknown address type: %s", parts[3]) err = fmt.Errorf("Unknown address type: %s", address)
} }
// If we have a server type, then record that. We default to net/rpc // If we have a server type, then record that. We default to net/rpc
@@ -773,6 +920,18 @@ func (c *Client) Start() (addr net.Addr, err error) {
return nil, fmt.Errorf("error parsing server cert: %s", err) return nil, fmt.Errorf("error parsing server cert: %s", err)
} }
} }
if c.config.GRPCBrokerMultiplex && c.protocol == ProtocolGRPC {
if len(parts) <= 6 {
return nil, fmt.Errorf("%w; for Go plugins, you will need to update the "+
"github.com/hashicorp/go-plugin dependency and recompile", ErrGRPCBrokerMuxNotSupported)
}
if muxSupported, err := strconv.ParseBool(parts[6]); err != nil {
return nil, fmt.Errorf("error parsing %q as a boolean for gRPC broker multiplexing support", parts[6])
} else if !muxSupported {
return nil, ErrGRPCBrokerMuxNotSupported
}
}
} }
c.address = addr c.address = addr
@@ -802,39 +961,30 @@ func (c *Client) loadServerCert(cert string) error {
} }
func (c *Client) reattach() (net.Addr, error) { func (c *Client) reattach() (net.Addr, error) {
// Verify the process still exists. If not, then it is an error reattachFunc := c.config.Reattach.ReattachFunc
p, err := os.FindProcess(c.config.Reattach.Pid) // For backwards compatibility default to cmdrunner.ReattachFunc
if err != nil { if reattachFunc == nil {
// On Unix systems, FindProcess never returns an error. reattachFunc = cmdrunner.ReattachFunc(c.config.Reattach.Pid, c.config.Reattach.Addr)
// On Windows, for non-existent pids it returns:
// os.SyscallError - 'OpenProcess: the paremter is incorrect'
return nil, ErrProcessNotFound
} }
// Attempt to connect to the addr since on Unix systems FindProcess r, err := reattachFunc()
// doesn't actually return an error if it can't find the process.
conn, err := net.Dial(
c.config.Reattach.Addr.Network(),
c.config.Reattach.Addr.String())
if err != nil { if err != nil {
p.Kill() return nil, err
return nil, ErrProcessNotFound
} }
conn.Close()
// Create a context for when we kill // Create a context for when we kill
c.doneCtx, c.ctxCancel = context.WithCancel(context.Background()) c.doneCtx, c.ctxCancel = context.WithCancel(context.Background())
c.clientWaitGroup.Add(1) c.clientWaitGroup.Add(1)
// Goroutine to mark exit status // Goroutine to mark exit status
go func(pid int) { go func(r runner.AttachedRunner) {
defer c.clientWaitGroup.Done() defer c.clientWaitGroup.Done()
// ensure the context is cancelled when we're done // ensure the context is cancelled when we're done
defer c.ctxCancel() defer c.ctxCancel()
// Wait for the process to die // Wait for the process to die
pidWait(pid) r.Wait(context.Background())
// Log so we can see it // Log so we can see it
c.logger.Debug("reattached plugin process exited") c.logger.Debug("reattached plugin process exited")
@@ -843,7 +993,7 @@ func (c *Client) reattach() (net.Addr, error) {
c.l.Lock() c.l.Lock()
defer c.l.Unlock() defer c.l.Unlock()
c.exited = true c.exited = true
}(p.Pid) }(r)
// Set the address and protocol // Set the address and protocol
c.address = c.config.Reattach.Addr c.address = c.config.Reattach.Addr
@@ -855,13 +1005,12 @@ func (c *Client) reattach() (net.Addr, error) {
if c.config.Reattach.Test { if c.config.Reattach.Test {
c.negotiatedVersion = c.config.Reattach.ProtocolVersion c.negotiatedVersion = c.config.Reattach.ProtocolVersion
} } else {
// If we're in test mode, we do NOT set the runner. This avoids the
// If we're in test mode, we do NOT set the process. This avoids the // runner being killed (the only purpose we have for setting c.runner
// process being killed (the only purpose we have for c.process), since // when reattaching), since in test mode the process is responsible for
// in test mode the process is responsible for exiting on its own. // exiting on its own.
if !c.config.Reattach.Test { c.runner = r
c.process = p
} }
return c.address, nil return c.address, nil
@@ -900,6 +1049,9 @@ func (c *Client) checkProtoVersion(protoVersion string) (int, PluginSet, error)
// //
// If this returns nil then the process hasn't been started yet. Please // If this returns nil then the process hasn't been started yet. Please
// call Start or Client before calling this. // call Start or Client before calling this.
//
// Clients who specified a RunnerFunc will need to populate their own
// ReattachFunc in the returned ReattachConfig before it can be used.
func (c *Client) ReattachConfig() *ReattachConfig { func (c *Client) ReattachConfig() *ReattachConfig {
c.l.Lock() c.l.Lock()
defer c.l.Unlock() defer c.l.Unlock()
@@ -917,11 +1069,16 @@ func (c *Client) ReattachConfig() *ReattachConfig {
return c.config.Reattach return c.config.Reattach
} }
return &ReattachConfig{ reattach := &ReattachConfig{
Protocol: c.protocol, Protocol: c.protocol,
Addr: c.address, Addr: c.address,
Pid: c.config.Cmd.Process.Pid,
} }
if c.config.Cmd != nil && c.config.Cmd.Process != nil {
reattach.Pid = c.config.Cmd.Process.Pid
}
return reattach
} }
// Protocol returns the protocol of server on the remote end. This will // Protocol returns the protocol of server on the remote end. This will
@@ -957,11 +1114,24 @@ func netAddrDialer(addr net.Addr) func(string, time.Duration) (net.Conn, error)
// dialer is compatible with grpc.WithDialer and creates the connection // dialer is compatible with grpc.WithDialer and creates the connection
// to the plugin. // to the plugin.
func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) { func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) {
conn, err := netAddrDialer(c.address)("", timeout) muxer, err := c.getGRPCMuxer(c.address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var conn net.Conn
if muxer.Enabled() {
conn, err = muxer.Dial()
if err != nil {
return nil, err
}
} else {
conn, err = netAddrDialer(c.address)("", timeout)
if err != nil {
return nil, err
}
}
// If we have a TLS config we wrap our connection. We only do this // If we have a TLS config we wrap our connection. We only do this
// for net/rpc since gRPC uses its own mechanism for TLS. // for net/rpc since gRPC uses its own mechanism for TLS.
if c.protocol == ProtocolNetRPC && c.config.TLSConfig != nil { if c.protocol == ProtocolNetRPC && c.config.TLSConfig != nil {
@@ -971,14 +1141,28 @@ func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) {
return conn, nil return conn, nil
} }
var stdErrBufferSize = 64 * 1024 func (c *Client) getGRPCMuxer(addr net.Addr) (*grpcmux.GRPCClientMuxer, error) {
if c.protocol != ProtocolGRPC || !c.config.GRPCBrokerMultiplex {
return nil, nil
}
func (c *Client) logStderr(r io.Reader) { var err error
c.grpcMuxerOnce.Do(func() {
c.grpcMuxer, err = grpcmux.NewGRPCClientMuxer(c.logger, addr)
})
if err != nil {
return nil, err
}
return c.grpcMuxer, nil
}
func (c *Client) logStderr(name string, r io.Reader) {
defer c.clientWaitGroup.Done() defer c.clientWaitGroup.Done()
defer c.stderrWaitGroup.Done() defer c.stderrWaitGroup.Done()
l := c.logger.Named(filepath.Base(c.config.Cmd.Path)) l := c.logger.Named(filepath.Base(name))
reader := bufio.NewReaderSize(r, stdErrBufferSize) reader := bufio.NewReaderSize(r, c.config.PluginLogBufferSize)
// continuation indicates the previous line was a prefix // continuation indicates the previous line was a prefix
continuation := false continuation := false
+16
View File
@@ -0,0 +1,16 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin
const (
// EnvUnixSocketDir specifies the directory that _plugins_ should create unix
// sockets in. Does not affect client behavior.
EnvUnixSocketDir = "PLUGIN_UNIX_SOCKET_DIR"
// EnvUnixSocketGroup specifies the owning, writable group to set for Unix
// sockets created by _plugins_. Does not affect client behavior.
EnvUnixSocketGroup = "PLUGIN_UNIX_SOCKET_GROUP"
envMultiplexGRPC = "PLUGIN_MULTIPLEX_GRPC"
)
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin package plugin
import ( import (
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package plugin package plugin
// This is a type that wraps error types so that they can be messaged // This is a type that wraps error types so that they can be messaged

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