diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57b9f77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/idas +/.idea +/dist diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d136f1c --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,67 @@ +run: + go: '1.18' + deadline: 5m + skip-files: + # Skip autogenerated files. + - ^.*\.(pb|y)\.go$ + +output: + sort-results: true + +linters: + enable: + - depguard + - gofumpt + - goimports + - revive + - misspell + - staticcheck + +issues: + exclude-rules: + - path: _test.go + linters: + - errcheck + +linters-settings: + + depguard: + list-type: blacklist + include-go-root: true + packages-with-error-message: + - sync/atomic: "Use go.uber.org/atomic instead of sync/atomic" + - github.com/stretchr/testify/assert: "Use github.com/stretchr/testify/require instead of github.com/stretchr/testify/assert" + - github.com/go-kit/kit/log: "Use github.com/go-kit/log instead of github.com/go-kit/kit/log" + errcheck: + exclude: scripts/errcheck_excludes.txt + goimports: + local-prefixes: idas + gofumpt: + lang-version: "1.15" + extra-rules: false + module-path: idas + revive: + rules: + - name: exported + arguments: ["disableStutteringCheck"] + - name: blank-imports + - name: context-as-argument + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: if-return + - name: increment-decrement + - name: var-naming + severity: warning + disabled: false + arguments: + - [ "ID" ] # AllowList + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d3b0f25 --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ + +GO ?= go +GOFMT ?= $(GO)fmt +FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) +GOOPTS ?= +GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) +GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) +GO_VERSION ?= $(shell $(GO) version) +GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) + +GOLANGCI_LINT := +GOLANGCI_LINT_OPTS ?= +GOLANGCI_LINT_VERSION ?= v1.45.2 +# golangci-lint only supports linux, darwin and windows platforms on i386/amd64. +# windows isn't included here because of the path separator being different. +ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) + ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386)) + # If we're in CI and there is an Actions file, that means the linter + # is being run in Actions, so we don't need to run it here. + ifeq (,$(CIRCLE_JOB)) + GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint + else ifeq (,$(wildcard .github/workflows/golangci-lint.yml)) + GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint + endif + endif +endif + +pkgs = ./... + +probuf: + protoc -I$(shell go env GOMODCACHE)/github.com/gogo/protobuf@v1.3.2/protobuf/ -I. -I./config -I=$(GOPATH)/src \ + --go_out=Mgoogle/protobuf/duration.proto=github.com/gogo/protobuf/types:./config \ + ./config/config.proto + +idas: + go build -ldflags="-s -w" -o dist/idas ./cmd/idas + +.PHONY: common-lint +common-lint: $(GOLANGCI_LINT) +ifdef GOLANGCI_LINT + @echo ">> running golangci-lint" + $(GOLANGCI_LINT) run $(pkgs) +endif + +ifdef GOLANGCI_LINT +$(GOLANGCI_LINT): + mkdir -p $(FIRST_GOPATH)/bin + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ + | sed -e '/install -d/d' \ + | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) +endif diff --git a/cmd/idas/cmd/idas.go b/cmd/idas/cmd/idas.go new file mode 100644 index 0000000..e0cd652 --- /dev/null +++ b/cmd/idas/cmd/idas.go @@ -0,0 +1,208 @@ +/* +Copyright © 2020 NAME HERE + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + + "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/prometheus" + "github.com/go-kit/log" + "github.com/go-kit/log/level" + lightstep "github.com/lightstep/lightstep-tracer-go" + "github.com/oklog/oklog/pkg/group" + stdopentracing "github.com/opentracing/opentracing-go" + zipkinot "github.com/openzipkin-contrib/zipkin-go-opentracing" + "github.com/openzipkin/zipkin-go" + zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http" + stdprometheus "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/cobra" + "sourcegraph.com/sourcegraph/appdash" + appdashot "sourcegraph.com/sourcegraph/appdash/opentracing" + + "idas/config" + "idas/pkg/endpoint" + "idas/pkg/logs" + "idas/pkg/logs/flag" + "idas/pkg/service" + "idas/pkg/transport" + "idas/pkg/utils/signals" +) + +var ( + cfgFile string + logConfig logs.Config + debugAddr string + httpAddr string + zipkinURL string + zipkinBridge bool + lightstepToken string + appdashAddr string +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "gateway", + Short: "The idas gateway server.", + Long: `The idas gateway server.`, + RunE: func(cmd *cobra.Command, args []string) error { + logger := logs.New(&logConfig) + logs.SetRootLogger(logger) + return Run(context.Background(), logger, signals.SetupSignalHandler(logger)) + }, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +func Run(ctx context.Context, logger log.Logger, stopCh *signals.StopChan) (err error) { + var zipkinTracer *zipkin.Tracer + { + if zipkinURL != "" { + var ( + err error + hostPort = "localhost:80" + serviceName = "addsvc" + reporter = zipkinhttp.NewReporter(zipkinURL) + ) + defer reporter.Close() + zEP, _ := zipkin.NewEndpoint(serviceName, hostPort) + zipkinTracer, err = zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(zEP)) + if err != nil { + logger.Log("err", err) + os.Exit(1) + } + if !(zipkinBridge) { + logger.Log("tracer", "Zipkin", "type", "Native", "URL", zipkinURL) + } + } + } + // Determine which OpenTracing tracer to use. We'll pass the tracer to all the + // components that use it, as a dependency. + var tracer stdopentracing.Tracer + { + if zipkinBridge && zipkinTracer != nil { + logger.Log("tracer", "Zipkin", "type", "OpenTracing", "URL", zipkinURL) + tracer = zipkinot.Wrap(zipkinTracer) + zipkinTracer = nil // do not instrument with both native tracer and opentracing bridge + } else if lightstepToken != "" { + logger.Log("tracer", "LightStep") // probably don't want to print out the token :) + tracer = lightstep.NewTracer(lightstep.Options{ + AccessToken: lightstepToken, + }) + defer lightstep.Flush(ctx, tracer) + } else if appdashAddr != "" { + logger.Log("tracer", "Appdash", "addr", appdashAddr) + tracer = appdashot.NewTracer(appdash.NewRemoteCollector(appdashAddr)) + } else { + tracer = stdopentracing.GlobalTracer() // no-op + } + } + + // Create the (sparse) metrics we'll use in the service. They, too, are + // dependencies that we pass to components that use them. + var ( + duration metrics.Histogram + ) + { + // Endpoint-level metrics. + duration = prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ + Namespace: "example", + Subsystem: "addsvc", + Name: "request_duration_seconds", + Help: "Request duration in seconds.", + }, []string{"method", "success"}) + } + var ( + svc = service.New(ctx) + endpoints = endpoint.New(svc, logger, duration, tracer, zipkinTracer) + httpHandler = transport.NewHTTPHandler(endpoints, tracer, zipkinTracer, logger) + ) + var g group.Group + { + // The debug listener mounts the http.DefaultServeMux, and serves up + // stuff like the Prometheus metrics route, the Go debug and profiling + // routes, and so on. + debugListener, err := net.Listen("tcp", debugAddr) + if err != nil { + level.Error(logger).Log("transport", "debug/HTTP", "during", "Listen", "err", err) + os.Exit(1) + } + g.Add(func() error { + level.Info(logger).Log("transport", "debug/HTTP", "addr", debugAddr) + return http.Serve(debugListener, http.DefaultServeMux) + }, func(error) { + debugListener.Close() + }) + } + { + // The HTTP listener mounts the Go kit HTTP handler we created. + httpListener, err := net.Listen("tcp", httpAddr) + if err != nil { + level.Error(logger).Log("transport", "HTTP", "during", "Listen", "err", err) + os.Exit(1) + } + g.Add(func() error { + level.Info(logger).Log("transport", "HTTP", "addr", httpAddr) + return http.Serve(httpListener, httpHandler) + }, func(error) { + httpListener.Close() + }) + } + return g.Run() +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "./idas.yaml", "config file (default is $HOME/.gateway.yaml)") + + // log level and format + flag.AddFlags(rootCmd.PersistentFlags(), &logConfig) + + rootCmd.Flags().StringVar(&debugAddr, "debug.addr", ":8080", "Debug and metrics listen address") + rootCmd.Flags().StringVar(&httpAddr, "http-addr", ":8081", "HTTP listen address") + rootCmd.Flags().StringVar(&zipkinURL, "zipkin-url", "", "Enable Zipkin tracing via HTTP reporter URL e.g. http://localhost:9411/api/v2/spans") + rootCmd.Flags().BoolVar(&zipkinBridge, "zipkin-ot-bridge", false, "Use Zipkin OpenTracing bridge instead of native implementation") + rootCmd.Flags().StringVar(&lightstepToken, "lightstep-token", "", "Enable LightStep tracing via a LightStep access token") + rootCmd.Flags().StringVar(&appdashAddr, "appdash-addr", "", "Enable Appdash tracing via an Appdash server host:port") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile == "" { + cfgFile = "./idas.yaml" + } + if err := config.ReloadConfigFromFile(logs.New(logs.MustNewConfig("info", "logfmt")), cfgFile); err != nil { + panic(fmt.Errorf("初始化配置失败: %s", err)) + } +} diff --git a/cmd/idas/cmd/migrate.go b/cmd/idas/cmd/migrate.go new file mode 100644 index 0000000..60bb673 --- /dev/null +++ b/cmd/idas/cmd/migrate.go @@ -0,0 +1,49 @@ +/* +Copyright © 2020 NAME HERE + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "context" + + "github.com/spf13/cobra" + + "idas/pkg/logs" + "idas/pkg/service" + "idas/pkg/utils/signals" +) + +// migrateCmd represents the migrate command +var migrateCmd = &cobra.Command{ + Use: "migrate", + Short: "自动迁移工具", + Long: `自动迁移仅仅会创建表,缺少列和索引,并且不会改变现有列的类型或删除未使用的列以保护数据。`, + Run: func(cmd *cobra.Command, args []string) { + logger := logs.New(&logConfig) + logs.SetRootLogger(logger) + Migrate(context.Background(), signals.SetupSignalHandler(logger)) + }, +} + +func Migrate(ctx context.Context, stopCh *signals.StopChan) { + svc := service.New(ctx) + if err := svc.AutoMigrate(ctx); err != nil { + panic(err) + } +} + +func init() { + rootCmd.AddCommand(migrateCmd) +} diff --git a/cmd/idas/cmd/user.go b/cmd/idas/cmd/user.go new file mode 100644 index 0000000..f6993a7 --- /dev/null +++ b/cmd/idas/cmd/user.go @@ -0,0 +1,90 @@ +/* +Copyright © 2020 NAME HERE + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "os" + + "github.com/howeyc/gopass" + "github.com/spf13/cobra" + + "idas/pkg/logs" + "idas/pkg/service" + "idas/pkg/service/models" +) + +var ( + username string + password string + email string + fullName string + role string +) + +// migrateCmd represents the migrate command +var userCmd = &cobra.Command{ + Use: "user", + Short: "User manager", + Long: `User manager tools.`, + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, +} + +var userAddCmd = &cobra.Command{ + Use: "add", + Short: "create user", + Long: `create user.`, + RunE: func(cmd *cobra.Command, args []string) error { + logger := logs.New(&logConfig) + logs.SetRootLogger(logger) + svc := service.New(cmd.Context()) + if password == "-" { + p, err := gopass.GetPasswdPrompt("please input password: ", true, os.Stdin, os.Stderr) + if err != nil { + return err + } + password = string(p) + + } + if len(username) == 0 { + panic("username is null") + } + if len(password) == 0 { + panic("passworld is null") + } + _, _, err := svc.CreateUser(cmd.Context(), "", &models.User{ + Username: username, + Password: []byte(password), + Email: email, + FullName: fullName, + Role: models.UserRole(role), + Status: models.UserStatusNormal, + }) + return err + }, +} + +func init() { + userCmd.PersistentFlags().StringVarP(&username, "username", "u", "", "username (login name).") + userCmd.PersistentFlags().StringVarP(&password, "password", "p", "", "user password.") + userCmd.PersistentFlags().StringVarP(&email, "email", "e", "", "user email.") + userCmd.PersistentFlags().StringVarP(&fullName, "fullname", "f", "", "user full name.") + userCmd.PersistentFlags().StringVarP(&role, "role", "r", "user", "user/admin") + + rootCmd.AddCommand(userCmd) + userCmd.AddCommand(userAddCmd) +} diff --git a/cmd/idas/main.go b/cmd/idas/main.go new file mode 100644 index 0000000..a5386ac --- /dev/null +++ b/cmd/idas/main.go @@ -0,0 +1,22 @@ +/* +Copyright © 2020 NAME HERE + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package main + +import "idas/cmd/idas/cmd" + +func main() { + cmd.Execute() +} diff --git a/common-protos/Makefile b/common-protos/Makefile new file mode 100644 index 0000000..e3e1076 --- /dev/null +++ b/common-protos/Makefile @@ -0,0 +1,65 @@ +VERSION=3.9.1 +URL="https://raw.githubusercontent.com/protocolbuffers/protobuf/v${VERSION}/src/google/protobuf" + +regenerate: + go install github.com/gogo/protobuf/protoc-gen-gogotypes + go install github.com/gogo/protobuf/protoc-min-version + + protoc-min-version \ + --version="3.0.0" \ + --gogotypes_out=../types/ \ + -I=. \ + google/protobuf/any.proto \ + google/protobuf/type.proto \ + google/protobuf/empty.proto \ + google/protobuf/api.proto \ + google/protobuf/timestamp.proto \ + google/protobuf/duration.proto \ + google/protobuf/struct.proto \ + google/protobuf/wrappers.proto \ + google/protobuf/field_mask.proto \ + google/protobuf/source_context.proto + + mv ../types/google/protobuf/*.pb.go ../types/ || true + rmdir ../types/google/protobuf || true + rmdir ../types/google || true + +update: + go install github.com/gogo/protobuf/gogoreplace + + (cd ./google/protobuf && rm descriptor.proto; wget ${URL}/descriptor.proto) + # gogoprotobuf requires users to import gogo.proto which imports descriptor.proto + # The descriptor.proto is only compatible with proto3 just because of the reserved keyword. + # We remove it to stay compatible with previous versions of protoc before proto3 + gogoreplace 'reserved 38;' '//reserved 38;' ./google/protobuf/descriptor.proto + gogoreplace 'reserved 8;' '//reserved 8;' ./google/protobuf/descriptor.proto + gogoreplace 'reserved 9;' '//reserved 9;' ./google/protobuf/descriptor.proto + gogoreplace 'reserved 4;' '//reserved 4;' ./google/protobuf/descriptor.proto + gogoreplace 'reserved 5;' '//reserved 5;' ./google/protobuf/descriptor.proto + gogoreplace 'option go_package = "github.com/golang/protobuf/protoc-gen-go/descriptor;descriptor";' 'option go_package = "descriptor";' ./google/protobuf/descriptor.proto + + (cd ./google/protobuf/compiler && rm plugin.proto; wget ${URL}/compiler/plugin.proto) + gogoreplace 'option go_package = "github.com/golang/protobuf/protoc-gen-go/plugin;plugin_go";' 'option go_package = "plugin_go";' ./google/protobuf/compiler/plugin.proto + + (cd ./google/protobuf && rm any.proto; wget ${URL}/any.proto) + gogoreplace 'go_package = "github.com/golang/protobuf/ptypes/any";' 'go_package = "types";' ./google/protobuf/any.proto + (cd ./google/protobuf && rm empty.proto; wget ${URL}/empty.proto) + gogoreplace 'go_package = "github.com/golang/protobuf/ptypes/empty";' 'go_package = "types";' ./google/protobuf/empty.proto + (cd ./google/protobuf && rm timestamp.proto; wget ${URL}/timestamp.proto) + gogoreplace 'go_package = "github.com/golang/protobuf/ptypes/timestamp";' 'go_package = "types";' ./google/protobuf/timestamp.proto + (cd ./google/protobuf && rm duration.proto; wget ${URL}/duration.proto) + gogoreplace 'go_package = "github.com/golang/protobuf/ptypes/duration";' 'go_package = "types";' ./google/protobuf/duration.proto + (cd ./google/protobuf && rm struct.proto; wget ${URL}/struct.proto) + gogoreplace 'go_package = "github.com/golang/protobuf/ptypes/struct;structpb";' 'go_package = "types";' ./google/protobuf/struct.proto + (cd ./google/protobuf && rm wrappers.proto; wget ${URL}/wrappers.proto) + gogoreplace 'go_package = "github.com/golang/protobuf/ptypes/wrappers";' 'go_package = "types";' ./google/protobuf/wrappers.proto + (cd ./google/protobuf && rm field_mask.proto; wget ${URL}/field_mask.proto) + gogoreplace 'option go_package = "google.golang.org/genproto/protobuf/field_mask;field_mask";' 'option go_package = "types";' ./google/protobuf/field_mask.proto + (cd ./google/protobuf && rm api.proto; wget ${URL}/api.proto) + gogoreplace 'option go_package = "google.golang.org/genproto/protobuf/api;api";' 'option go_package = "types";' ./google/protobuf/api.proto + (cd ./google/protobuf && rm type.proto; wget ${URL}/type.proto) + gogoreplace 'option go_package = "google.golang.org/genproto/protobuf/ptype;ptype";' 'option go_package = "types";' ./google/protobuf/type.proto + (cd ./google/protobuf && rm source_context.proto; wget ${URL}/source_context.proto) + gogoreplace 'option go_package = "google.golang.org/genproto/protobuf/source_context;source_context";' 'option go_package = "types";' ./google/protobuf/source_context.proto + + diff --git a/common-protos/google/protobuf/any.proto b/common-protos/google/protobuf/any.proto new file mode 100644 index 0000000..4cf3843 --- /dev/null +++ b/common-protos/google/protobuf/any.proto @@ -0,0 +1,155 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "types"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := ptypes.MarshalAny(foo) +// ... +// foo := &pb.Foo{} +// if err := ptypes.UnmarshalAny(any, foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} diff --git a/common-protos/google/protobuf/api.proto b/common-protos/google/protobuf/api.proto new file mode 100644 index 0000000..67c1ddb --- /dev/null +++ b/common-protos/google/protobuf/api.proto @@ -0,0 +1,210 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/source_context.proto"; +import "google/protobuf/type.proto"; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "ApiProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "types"; + +// Api is a light-weight descriptor for an API Interface. +// +// Interfaces are also described as "protocol buffer services" in some contexts, +// such as by the "service" keyword in a .proto file, but they are different +// from API Services, which represent a concrete implementation of an interface +// as opposed to simply a description of methods and bindings. They are also +// sometimes simply referred to as "APIs" in other contexts, such as the name of +// this message itself. See https://cloud.google.com/apis/design/glossary for +// detailed terminology. +message Api { + + // The fully qualified name of this interface, including package name + // followed by the interface's simple name. + string name = 1; + + // The methods of this interface, in unspecified order. + repeated Method methods = 2; + + // Any metadata attached to the interface. + repeated Option options = 3; + + // A version string for this interface. If specified, must have the form + // `major-version.minor-version`, as in `1.10`. If the minor version is + // omitted, it defaults to zero. If the entire version field is empty, the + // major version is derived from the package name, as outlined below. If the + // field is not empty, the version in the package name will be verified to be + // consistent with what is provided here. + // + // The versioning schema uses [semantic + // versioning](http://semver.org) where the major version number + // indicates a breaking change and the minor version an additive, + // non-breaking change. Both version numbers are signals to users + // what to expect from different versions, and should be carefully + // chosen based on the product plan. + // + // The major version is also reflected in the package name of the + // interface, which must end in `v`, as in + // `google.feature.v1`. For major versions 0 and 1, the suffix can + // be omitted. Zero major versions must only be used for + // experimental, non-GA interfaces. + // + // + string version = 4; + + // Source context for the protocol buffer service represented by this + // message. + SourceContext source_context = 5; + + // Included interfaces. See [Mixin][]. + repeated Mixin mixins = 6; + + // The source syntax of the service. + Syntax syntax = 7; +} + +// Method represents a method of an API interface. +message Method { + + // The simple name of this method. + string name = 1; + + // A URL of the input message type. + string request_type_url = 2; + + // If true, the request is streamed. + bool request_streaming = 3; + + // The URL of the output message type. + string response_type_url = 4; + + // If true, the response is streamed. + bool response_streaming = 5; + + // Any metadata attached to the method. + repeated Option options = 6; + + // The source syntax of this method. + Syntax syntax = 7; +} + +// Declares an API Interface to be included in this interface. The including +// interface must redeclare all the methods from the included interface, but +// documentation and options are inherited as follows: +// +// - If after comment and whitespace stripping, the documentation +// string of the redeclared method is empty, it will be inherited +// from the original method. +// +// - Each annotation belonging to the service config (http, +// visibility) which is not set in the redeclared method will be +// inherited. +// +// - If an http annotation is inherited, the path pattern will be +// modified as follows. Any version prefix will be replaced by the +// version of the including interface plus the [root][] path if +// specified. +// +// Example of a simple mixin: +// +// package google.acl.v1; +// service AccessControl { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v1/{resource=**}:getAcl"; +// } +// } +// +// package google.storage.v2; +// service Storage { +// rpc GetAcl(GetAclRequest) returns (Acl); +// +// // Get a data record. +// rpc GetData(GetDataRequest) returns (Data) { +// option (google.api.http).get = "/v2/{resource=**}"; +// } +// } +// +// Example of a mixin configuration: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// +// The mixin construct implies that all methods in `AccessControl` are +// also declared with same name and request/response types in +// `Storage`. A documentation generator or annotation processor will +// see the effective `Storage.GetAcl` method after inherting +// documentation and annotations as follows: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/{resource=**}:getAcl"; +// } +// ... +// } +// +// Note how the version in the path pattern changed from `v1` to `v2`. +// +// If the `root` field in the mixin is specified, it should be a +// relative path under which inherited HTTP paths are placed. Example: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// root: acls +// +// This implies the following inherited HTTP annotation: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; +// } +// ... +// } +message Mixin { + // The fully qualified name of the interface which is included. + string name = 1; + + // If non-empty specifies a path under which inherited HTTP paths + // are rooted. + string root = 2; +} diff --git a/common-protos/google/protobuf/compiler/plugin.proto b/common-protos/google/protobuf/compiler/plugin.proto new file mode 100644 index 0000000..4a88adf --- /dev/null +++ b/common-protos/google/protobuf/compiler/plugin.proto @@ -0,0 +1,168 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// +// WARNING: The plugin interface is currently EXPERIMENTAL and is subject to +// change. +// +// protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is +// just a program that reads a CodeGeneratorRequest from stdin and writes a +// CodeGeneratorResponse to stdout. +// +// Plugins written using C++ can use google/protobuf/compiler/plugin.h instead +// of dealing with the raw protocol defined here. +// +// A plugin executable needs only to be placed somewhere in the path. The +// plugin should be named "protoc-gen-$NAME", and will then be used when the +// flag "--${NAME}_out" is passed to protoc. + +syntax = "proto2"; + +package google.protobuf.compiler; +option java_package = "com.google.protobuf.compiler"; +option java_outer_classname = "PluginProtos"; + +option go_package = "plugin_go"; + +import "google/protobuf/descriptor.proto"; + +// The version number of protocol compiler. +message Version { + optional int32 major = 1; + optional int32 minor = 2; + optional int32 patch = 3; + // A suffix for alpha, beta or rc release, e.g., "alpha-1", "rc2". It should + // be empty for mainline stable releases. + optional string suffix = 4; +} + +// An encoded CodeGeneratorRequest is written to the plugin's stdin. +message CodeGeneratorRequest { + // The .proto files that were explicitly listed on the command-line. The + // code generator should generate code only for these files. Each file's + // descriptor will be included in proto_file, below. + repeated string file_to_generate = 1; + + // The generator parameter passed on the command-line. + optional string parameter = 2; + + // FileDescriptorProtos for all files in files_to_generate and everything + // they import. The files will appear in topological order, so each file + // appears before any file that imports it. + // + // protoc guarantees that all proto_files will be written after + // the fields above, even though this is not technically guaranteed by the + // protobuf wire format. This theoretically could allow a plugin to stream + // in the FileDescriptorProtos and handle them one by one rather than read + // the entire set into memory at once. However, as of this writing, this + // is not similarly optimized on protoc's end -- it will store all fields in + // memory at once before sending them to the plugin. + // + // Type names of fields and extensions in the FileDescriptorProto are always + // fully qualified. + repeated FileDescriptorProto proto_file = 15; + + // The version number of protocol compiler. + optional Version compiler_version = 3; + +} + +// The plugin writes an encoded CodeGeneratorResponse to stdout. +message CodeGeneratorResponse { + // Error message. If non-empty, code generation failed. The plugin process + // should exit with status code zero even if it reports an error in this way. + // + // This should be used to indicate errors in .proto files which prevent the + // code generator from generating correct code. Errors which indicate a + // problem in protoc itself -- such as the input CodeGeneratorRequest being + // unparseable -- should be reported by writing a message to stderr and + // exiting with a non-zero status code. + optional string error = 1; + + // Represents a single generated file. + message File { + // The file name, relative to the output directory. The name must not + // contain "." or ".." components and must be relative, not be absolute (so, + // the file cannot lie outside the output directory). "/" must be used as + // the path separator, not "\". + // + // If the name is omitted, the content will be appended to the previous + // file. This allows the generator to break large files into small chunks, + // and allows the generated text to be streamed back to protoc so that large + // files need not reside completely in memory at one time. Note that as of + // this writing protoc does not optimize for this -- it will read the entire + // CodeGeneratorResponse before writing files to disk. + optional string name = 1; + + // If non-empty, indicates that the named file should already exist, and the + // content here is to be inserted into that file at a defined insertion + // point. This feature allows a code generator to extend the output + // produced by another code generator. The original generator may provide + // insertion points by placing special annotations in the file that look + // like: + // @@protoc_insertion_point(NAME) + // The annotation can have arbitrary text before and after it on the line, + // which allows it to be placed in a comment. NAME should be replaced with + // an identifier naming the point -- this is what other generators will use + // as the insertion_point. Code inserted at this point will be placed + // immediately above the line containing the insertion point (thus multiple + // insertions to the same point will come out in the order they were added). + // The double-@ is intended to make it unlikely that the generated code + // could contain things that look like insertion points by accident. + // + // For example, the C++ code generator places the following line in the + // .pb.h files that it generates: + // // @@protoc_insertion_point(namespace_scope) + // This line appears within the scope of the file's package namespace, but + // outside of any particular class. Another plugin can then specify the + // insertion_point "namespace_scope" to generate additional classes or + // other declarations that should be placed in this scope. + // + // Note that if the line containing the insertion point begins with + // whitespace, the same whitespace will be added to every line of the + // inserted text. This is useful for languages like Python, where + // indentation matters. In these languages, the insertion point comment + // should be indented the same amount as any inserted code will need to be + // in order to work correctly in that context. + // + // The code generator that generates the initial file and the one which + // inserts into it must both run as part of a single invocation of protoc. + // Code generators are executed in the order in which they appear on the + // command line. + // + // If |insertion_point| is present, |name| must also be present. + optional string insertion_point = 2; + + // The file contents. + optional string content = 15; + } + repeated File file = 15; +} diff --git a/common-protos/google/protobuf/descriptor.proto b/common-protos/google/protobuf/descriptor.proto new file mode 100644 index 0000000..4a08905 --- /dev/null +++ b/common-protos/google/protobuf/descriptor.proto @@ -0,0 +1,885 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + + +syntax = "proto2"; + +package google.protobuf; + +option go_package = "descriptor"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2" and "proto3". + optional string syntax = 12; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; +} + +message ExtensionRangeOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported in proto3. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REQUIRED = 2; + LABEL_REPEATED = 3; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + // TODO(kenton): Base-64 encode? + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + + // If set, all the classes from the .proto file are wrapped in a single + // outer class with the given name. This applies to both Proto1 + // (equivalent to the old "--one_java_file" option) and Proto2 (where + // a .proto always translates to a single class, but you may want to + // explicitly choose the class name). + optional string java_outer_classname = 8; + + // If set true, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the outer class + // named by java_outer_classname. However, the outer class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [default = false]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // If set true, then the Java2 code generator will generate code that + // throws an exception whenever an attempt is made to assign a non-UTF-8 + // byte sequence to a string field. + // Message reflection will do the same. + // However, an extension field still accepts non-UTF-8 byte sequences. + // This option has no effect on when used with the lite runtime. + optional bool java_string_check_utf8 = 27 [default = false]; + + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + + + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + optional bool php_generic_services = 42 [default = false]; + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = false]; + + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + //reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + // + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + optional bool map_entry = 7; + + //reserved 8; // javalite_serializable + //reserved 9; // javanano_as_lite + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is not yet implemented in the open source + // release -- sorry, we'll try to include it in a future version! + optional CType ctype = 1 [default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // + // Note that implementations may choose not to check required fields within + // a lazy sub-message. That is, calling IsInitialized() on the outer message + // may return true even if the inner message has missing required fields. + // This is necessary because otherwise the inner message would have to be + // parsed in order to perform the check, defeating the purpose of lazy + // parsing. An implementation which chooses not to check required fields + // must be consistent about it. That is, for any particular sub-message, the + // implementation must either *always* check its required fields, or *never* + // check its required fields, regardless of whether or not the message has + // been parsed. + optional bool lazy = 5 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false]; + + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + //reserved 4; // removed jtype +} + +message OneofOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + //reserved 5; // javanano_as_lite + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["qux", false] } represents + // "foo.(bar.baz).qux". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition. For + // example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to qux. + // // + // // Another line attached to qux. + // optional double qux = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to qux or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified offset. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + } +} diff --git a/common-protos/google/protobuf/duration.proto b/common-protos/google/protobuf/duration.proto new file mode 100644 index 0000000..b14bea5 --- /dev/null +++ b/common-protos/google/protobuf/duration.proto @@ -0,0 +1,116 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "types"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (durations.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// In JSON format, the Duration type is encoded as a string rather than an +// object, where the string ends in the suffix "s" (indicating seconds) and +// is preceded by the number of seconds, with nanoseconds expressed as +// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +// microsecond should be expressed in JSON format as "3.000001s". +// +// +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. Note: these bounds are computed from: + // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + int32 nanos = 2; +} diff --git a/common-protos/google/protobuf/empty.proto b/common-protos/google/protobuf/empty.proto new file mode 100644 index 0000000..6057c85 --- /dev/null +++ b/common-protos/google/protobuf/empty.proto @@ -0,0 +1,52 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "types"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// A generic empty message that you can re-use to avoid defining duplicated +// empty messages in your APIs. A typical example is to use it as the request +// or the response type of an API method. For instance: +// +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } +// +// The JSON representation for `Empty` is empty JSON object `{}`. +message Empty {} diff --git a/common-protos/google/protobuf/field_mask.proto b/common-protos/google/protobuf/field_mask.proto new file mode 100644 index 0000000..7b77007 --- /dev/null +++ b/common-protos/google/protobuf/field_mask.proto @@ -0,0 +1,245 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "FieldMaskProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "types"; +option cc_enable_arenas = true; + +// `FieldMask` represents a set of symbolic field paths, for example: +// +// paths: "f.a" +// paths: "f.b.d" +// +// Here `f` represents a field in some root message, `a` and `b` +// fields in the message found in `f`, and `d` a field found in the +// message in `f.b`. +// +// Field masks are used to specify a subset of fields that should be +// returned by a get operation or modified by an update operation. +// Field masks also have a custom JSON encoding (see below). +// +// # Field Masks in Projections +// +// When used in the context of a projection, a response message or +// sub-message is filtered by the API to only contain those fields as +// specified in the mask. For example, if the mask in the previous +// example is applied to a response message as follows: +// +// f { +// a : 22 +// b { +// d : 1 +// x : 2 +// } +// y : 13 +// } +// z: 8 +// +// The result will not contain specific values for fields x,y and z +// (their value will be set to the default, and omitted in proto text +// output): +// +// +// f { +// a : 22 +// b { +// d : 1 +// } +// } +// +// A repeated field is not allowed except at the last position of a +// paths string. +// +// If a FieldMask object is not present in a get operation, the +// operation applies to all fields (as if a FieldMask of all fields +// had been specified). +// +// Note that a field mask does not necessarily apply to the +// top-level response message. In case of a REST get operation, the +// field mask applies directly to the response, but in case of a REST +// list operation, the mask instead applies to each individual message +// in the returned resource list. In case of a REST custom method, +// other definitions may be used. Where the mask applies will be +// clearly documented together with its declaration in the API. In +// any case, the effect on the returned resource/resources is required +// behavior for APIs. +// +// # Field Masks in Update Operations +// +// A field mask in update operations specifies which fields of the +// targeted resource are going to be updated. The API is required +// to only change the values of the fields as specified in the mask +// and leave the others untouched. If a resource is passed in to +// describe the updated values, the API ignores the values of all +// fields not covered by the mask. +// +// If a repeated field is specified for an update operation, new values will +// be appended to the existing repeated field in the target resource. Note that +// a repeated field is only allowed in the last position of a `paths` string. +// +// If a sub-message is specified in the last position of the field mask for an +// update operation, then new value will be merged into the existing sub-message +// in the target resource. +// +// For example, given the target message: +// +// f { +// b { +// d: 1 +// x: 2 +// } +// c: [1] +// } +// +// And an update message: +// +// f { +// b { +// d: 10 +// } +// c: [2] +// } +// +// then if the field mask is: +// +// paths: ["f.b", "f.c"] +// +// then the result will be: +// +// f { +// b { +// d: 10 +// x: 2 +// } +// c: [1, 2] +// } +// +// An implementation may provide options to override this default behavior for +// repeated and message fields. +// +// In order to reset a field's value to the default, the field must +// be in the mask and set to the default value in the provided resource. +// Hence, in order to reset all fields of a resource, provide a default +// instance of the resource and set all fields in the mask, or do +// not provide a mask as described below. +// +// If a field mask is not present on update, the operation applies to +// all fields (as if a field mask of all fields has been specified). +// Note that in the presence of schema evolution, this may mean that +// fields the client does not know and has therefore not filled into +// the request will be reset to their default. If this is unwanted +// behavior, a specific service may require a client to always specify +// a field mask, producing an error if not. +// +// As with get operations, the location of the resource which +// describes the updated values in the request message depends on the +// operation kind. In any case, the effect of the field mask is +// required to be honored by the API. +// +// ## Considerations for HTTP REST +// +// The HTTP kind of an update operation which uses a field mask must +// be set to PATCH instead of PUT in order to satisfy HTTP semantics +// (PUT must only be used for full updates). +// +// # JSON Encoding of Field Masks +// +// In JSON, a field mask is encoded as a single string where paths are +// separated by a comma. Fields name in each path are converted +// to/from lower-camel naming conventions. +// +// As an example, consider the following message declarations: +// +// message Profile { +// User user = 1; +// Photo photo = 2; +// } +// message User { +// string display_name = 1; +// string address = 2; +// } +// +// In proto a field mask for `Profile` may look as such: +// +// mask { +// paths: "user.display_name" +// paths: "photo" +// } +// +// In JSON, the same mask is represented as below: +// +// { +// mask: "user.displayName,photo" +// } +// +// # Field Masks and Oneof Fields +// +// Field masks treat fields in oneofs just as regular fields. Consider the +// following message: +// +// message SampleMessage { +// oneof test_oneof { +// string name = 4; +// SubMessage sub_message = 9; +// } +// } +// +// The field mask can be: +// +// mask { +// paths: "name" +// } +// +// Or: +// +// mask { +// paths: "sub_message" +// } +// +// Note that oneof type names ("test_oneof" in this case) cannot be used in +// paths. +// +// ## Field Mask Verification +// +// The implementation of any API method which has a FieldMask type field in the +// request should verify the included field paths, and return an +// `INVALID_ARGUMENT` error if any path is duplicated or unmappable. +message FieldMask { + // The set of field mask paths. + repeated string paths = 1; +} diff --git a/common-protos/google/protobuf/source_context.proto b/common-protos/google/protobuf/source_context.proto new file mode 100644 index 0000000..8654578 --- /dev/null +++ b/common-protos/google/protobuf/source_context.proto @@ -0,0 +1,48 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "SourceContextProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "types"; + +// `SourceContext` represents information about the source of a +// protobuf element, like the file in which it is defined. +message SourceContext { + // The path-qualified name of the .proto file that contained the associated + // protobuf element. For example: `"google/protobuf/source_context.proto"`. + string file_name = 1; +} diff --git a/common-protos/google/protobuf/struct.proto b/common-protos/google/protobuf/struct.proto new file mode 100644 index 0000000..9db0771 --- /dev/null +++ b/common-protos/google/protobuf/struct.proto @@ -0,0 +1,95 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "types"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "StructProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// `Struct` represents a structured data value, consisting of fields +// which map to dynamically typed values. In some languages, `Struct` +// might be supported by a native representation. For example, in +// scripting languages like JS a struct is represented as an +// object. The details of that representation are described together +// with the proto support for the language. +// +// The JSON representation for `Struct` is JSON object. +message Struct { + // Unordered map of dynamically typed values. + map fields = 1; +} + +// `Value` represents a dynamically typed value which can be either +// null, a number, a string, a boolean, a recursive struct value, or a +// list of values. A producer of value is expected to set one of that +// variants, absence of any variant indicates an error. +// +// The JSON representation for `Value` is JSON value. +message Value { + // The kind of value. + oneof kind { + // Represents a null value. + NullValue null_value = 1; + // Represents a double value. + double number_value = 2; + // Represents a string value. + string string_value = 3; + // Represents a boolean value. + bool bool_value = 4; + // Represents a structured value. + Struct struct_value = 5; + // Represents a repeated `Value`. + ListValue list_value = 6; + } +} + +// `NullValue` is a singleton enumeration to represent the null value for the +// `Value` type union. +// +// The JSON representation for `NullValue` is JSON `null`. +enum NullValue { + // Null value. + NULL_VALUE = 0; +} + +// `ListValue` is a wrapper around a repeated field of values. +// +// The JSON representation for `ListValue` is JSON array. +message ListValue { + // Repeated field of dynamically typed values. + repeated Value values = 1; +} diff --git a/common-protos/google/protobuf/timestamp.proto b/common-protos/google/protobuf/timestamp.proto new file mode 100644 index 0000000..0ebe36e --- /dev/null +++ b/common-protos/google/protobuf/timestamp.proto @@ -0,0 +1,138 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "types"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// +// Example 5: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A proto3 JSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a proto3 JSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D +// ) to obtain a formatter capable of generating timestamps in this format. +// +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/common-protos/google/protobuf/type.proto b/common-protos/google/protobuf/type.proto new file mode 100644 index 0000000..cc62625 --- /dev/null +++ b/common-protos/google/protobuf/type.proto @@ -0,0 +1,187 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/any.proto"; +import "google/protobuf/source_context.proto"; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TypeProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option go_package = "types"; + +// A protocol buffer message type. +message Type { + // The fully qualified message name. + string name = 1; + // The list of fields. + repeated Field fields = 2; + // The list of types appearing in `oneof` definitions in this type. + repeated string oneofs = 3; + // The protocol buffer options. + repeated Option options = 4; + // The source context. + SourceContext source_context = 5; + // The source syntax. + Syntax syntax = 6; +} + +// A single field of a message type. +message Field { + // Basic field types. + enum Kind { + // Field type unknown. + TYPE_UNKNOWN = 0; + // Field type double. + TYPE_DOUBLE = 1; + // Field type float. + TYPE_FLOAT = 2; + // Field type int64. + TYPE_INT64 = 3; + // Field type uint64. + TYPE_UINT64 = 4; + // Field type int32. + TYPE_INT32 = 5; + // Field type fixed64. + TYPE_FIXED64 = 6; + // Field type fixed32. + TYPE_FIXED32 = 7; + // Field type bool. + TYPE_BOOL = 8; + // Field type string. + TYPE_STRING = 9; + // Field type group. Proto2 syntax only, and deprecated. + TYPE_GROUP = 10; + // Field type message. + TYPE_MESSAGE = 11; + // Field type bytes. + TYPE_BYTES = 12; + // Field type uint32. + TYPE_UINT32 = 13; + // Field type enum. + TYPE_ENUM = 14; + // Field type sfixed32. + TYPE_SFIXED32 = 15; + // Field type sfixed64. + TYPE_SFIXED64 = 16; + // Field type sint32. + TYPE_SINT32 = 17; + // Field type sint64. + TYPE_SINT64 = 18; + } + + // Whether a field is optional, required, or repeated. + enum Cardinality { + // For fields with unknown cardinality. + CARDINALITY_UNKNOWN = 0; + // For optional fields. + CARDINALITY_OPTIONAL = 1; + // For required fields. Proto2 syntax only. + CARDINALITY_REQUIRED = 2; + // For repeated fields. + CARDINALITY_REPEATED = 3; + }; + + // The field type. + Kind kind = 1; + // The field cardinality. + Cardinality cardinality = 2; + // The field number. + int32 number = 3; + // The field name. + string name = 4; + // The field type URL, without the scheme, for message or enumeration + // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. + string type_url = 6; + // The index of the field type in `Type.oneofs`, for message or enumeration + // types. The first type has index 1; zero means the type is not in the list. + int32 oneof_index = 7; + // Whether to use alternative packed wire representation. + bool packed = 8; + // The protocol buffer options. + repeated Option options = 9; + // The field JSON name. + string json_name = 10; + // The string value of the default value of this field. Proto2 syntax only. + string default_value = 11; +} + +// Enum type definition. +message Enum { + // Enum type name. + string name = 1; + // Enum value definitions. + repeated EnumValue enumvalue = 2; + // Protocol buffer options. + repeated Option options = 3; + // The source context. + SourceContext source_context = 4; + // The source syntax. + Syntax syntax = 5; +} + +// Enum value definition. +message EnumValue { + // Enum value name. + string name = 1; + // Enum value number. + int32 number = 2; + // Protocol buffer options. + repeated Option options = 3; +} + +// A protocol buffer option, which can be attached to a message, field, +// enumeration, etc. +message Option { + // The option's name. For protobuf built-in options (options defined in + // descriptor.proto), this is the short name. For example, `"map_entry"`. + // For custom options, it should be the fully-qualified name. For example, + // `"google.api.http"`. + string name = 1; + // The option's value packed in an Any message. If the value is a primitive, + // the corresponding wrapper type defined in google/protobuf/wrappers.proto + // should be used. If the value is an enum, it should be stored as an int32 + // value using the google.protobuf.Int32Value type. + Any value = 2; +} + +// The syntax in which a protocol buffer element is defined. +enum Syntax { + // Syntax `proto2`. + SYNTAX_PROTO2 = 0; + // Syntax `proto3`. + SYNTAX_PROTO3 = 1; +} diff --git a/common-protos/google/protobuf/wrappers.proto b/common-protos/google/protobuf/wrappers.proto new file mode 100644 index 0000000..59b76ac --- /dev/null +++ b/common-protos/google/protobuf/wrappers.proto @@ -0,0 +1,123 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Wrappers for primitive (non-message) types. These types are useful +// for embedding primitives in the `google.protobuf.Any` type and for places +// where we need to distinguish between the absence of a primitive +// typed field and its default value. +// +// These wrappers have no meaningful use within repeated fields as they lack +// the ability to detect presence on individual elements. +// These wrappers have no meaningful use within a map or a oneof since +// individual entries of a map or fields of a oneof can already detect presence. + +syntax = "proto3"; + +package google.protobuf; + +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; +option go_package = "types"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "WrappersProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; + +// Wrapper message for `double`. +// +// The JSON representation for `DoubleValue` is JSON number. +message DoubleValue { + // The double value. + double value = 1; +} + +// Wrapper message for `float`. +// +// The JSON representation for `FloatValue` is JSON number. +message FloatValue { + // The float value. + float value = 1; +} + +// Wrapper message for `int64`. +// +// The JSON representation for `Int64Value` is JSON string. +message Int64Value { + // The int64 value. + int64 value = 1; +} + +// Wrapper message for `uint64`. +// +// The JSON representation for `UInt64Value` is JSON string. +message UInt64Value { + // The uint64 value. + uint64 value = 1; +} + +// Wrapper message for `int32`. +// +// The JSON representation for `Int32Value` is JSON number. +message Int32Value { + // The int32 value. + int32 value = 1; +} + +// Wrapper message for `uint32`. +// +// The JSON representation for `UInt32Value` is JSON number. +message UInt32Value { + // The uint32 value. + uint32 value = 1; +} + +// Wrapper message for `bool`. +// +// The JSON representation for `BoolValue` is JSON `true` and `false`. +message BoolValue { + // The bool value. + bool value = 1; +} + +// Wrapper message for `string`. +// +// The JSON representation for `StringValue` is JSON string. +message StringValue { + // The string value. + string value = 1; +} + +// Wrapper message for `bytes`. +// +// The JSON representation for `BytesValue` is JSON string. +message BytesValue { + // The bytes value. + bytes value = 1; +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..a31e77d --- /dev/null +++ b/config/config.go @@ -0,0 +1,45 @@ +package config + +import ( + "encoding/json" + "time" + + "github.com/go-kit/log" + "github.com/gogo/protobuf/types" + "github.com/golang/protobuf/jsonpb" +) + +func (x *Config) Init(logger log.Logger) error { + return nil +} + +func (x *MySQLOptions) GetStdMaxConnectionLifeTime() time.Duration { + if x != nil { + if duration, err := types.DurationFromProto(x.MaxConnectionLifeTime); err == nil { + return duration + } + } + return time.Second * 30 +} + +func (x *MySQLOptions) UnmarshalJSONPB(_ *jsonpb.Unmarshaler, b []byte) error { + options := NewMySQLOptions() + x.Charset = options.Charset + x.Collation = options.Collation + x.MaxIdleConnections = options.MaxIdleConnections + x.MaxOpenConnections = options.MaxOpenConnections + x.MaxConnectionLifeTime = options.MaxConnectionLifeTime + x.TablePrefix = options.TablePrefix + return json.Unmarshal(b, x) +} + +func NewMySQLOptions() *MySQLOptions { + return &MySQLOptions{ + Charset: "utf8", + Collation: "utf8_general_ci", + MaxIdleConnections: 2, + MaxOpenConnections: 100, + MaxConnectionLifeTime: types.DurationProto(30 * time.Second), + TablePrefix: "t_", + } +} diff --git a/config/config.pb.go b/config/config.pb.go new file mode 100644 index 0000000..fcaeed0 --- /dev/null +++ b/config/config.pb.go @@ -0,0 +1,646 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.19.4 +// source: config/config.proto + +package config + +import ( + types "github.com/gogo/protobuf/types" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Symbols defined in public import of google/protobuf/duration.proto. + +type Duration = types.Duration + +type RedisOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *RedisOptions) Reset() { + *x = RedisOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_config_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RedisOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RedisOptions) ProtoMessage() {} + +func (x *RedisOptions) ProtoReflect() protoreflect.Message { + mi := &file_config_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RedisOptions.ProtoReflect.Descriptor instead. +func (*RedisOptions) Descriptor() ([]byte, []int) { + return file_config_config_proto_rawDescGZIP(), []int{0} +} + +func (x *RedisOptions) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type LdapOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *LdapOptions) Reset() { + *x = LdapOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_config_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LdapOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LdapOptions) ProtoMessage() {} + +func (x *LdapOptions) ProtoReflect() protoreflect.Message { + mi := &file_config_config_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LdapOptions.ProtoReflect.Descriptor instead. +func (*LdapOptions) Descriptor() ([]byte, []int) { + return file_config_config_proto_rawDescGZIP(), []int{1} +} + +type MySQLOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Host string `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"` + Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + Schema string `protobuf:"bytes,4,opt,name=schema,proto3" json:"schema,omitempty"` + MaxIdle int32 `protobuf:"varint,5,opt,name=max_idle,json=maxIdle,proto3" json:"max_idle,omitempty"` + MaxIdleConnections int32 `protobuf:"varint,6,opt,name=max_idle_connections,json=maxIdleConnections,proto3" json:"max_idle_connections,omitempty"` + MaxOpenConnections int32 `protobuf:"varint,7,opt,name=max_open_connections,json=maxOpenConnections,proto3" json:"max_open_connections,omitempty"` + MaxConnectionLifeTime *types.Duration `protobuf:"bytes,8,opt,name=max_connection_lifeTime,json=maxConnectionLifeTime,proto3" json:"max_connection_lifeTime,omitempty"` + Charset string `protobuf:"bytes,9,opt,name=charset,proto3" json:"charset,omitempty"` + Collation string `protobuf:"bytes,10,opt,name=collation,proto3" json:"collation,omitempty"` + TablePrefix string `protobuf:"bytes,11,opt,name=table_prefix,json=tablePrefix,proto3" json:"table_prefix,omitempty"` +} + +func (x *MySQLOptions) Reset() { + *x = MySQLOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_config_config_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MySQLOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MySQLOptions) ProtoMessage() {} + +func (x *MySQLOptions) ProtoReflect() protoreflect.Message { + mi := &file_config_config_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MySQLOptions.ProtoReflect.Descriptor instead. +func (*MySQLOptions) Descriptor() ([]byte, []int) { + return file_config_config_proto_rawDescGZIP(), []int{2} +} + +func (x *MySQLOptions) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *MySQLOptions) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *MySQLOptions) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *MySQLOptions) GetSchema() string { + if x != nil { + return x.Schema + } + return "" +} + +func (x *MySQLOptions) GetMaxIdle() int32 { + if x != nil { + return x.MaxIdle + } + return 0 +} + +func (x *MySQLOptions) GetMaxIdleConnections() int32 { + if x != nil { + return x.MaxIdleConnections + } + return 0 +} + +func (x *MySQLOptions) GetMaxOpenConnections() int32 { + if x != nil { + return x.MaxOpenConnections + } + return 0 +} + +func (x *MySQLOptions) GetMaxConnectionLifeTime() *types.Duration { + if x != nil { + return x.MaxConnectionLifeTime + } + return nil +} + +func (x *MySQLOptions) GetCharset() string { + if x != nil { + return x.Charset + } + return "" +} + +func (x *MySQLOptions) GetCollation() string { + if x != nil { + return x.Collation + } + return "" +} + +func (x *MySQLOptions) GetTablePrefix() string { + if x != nil { + return x.TablePrefix + } + return "" +} + +type Storage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Types that are assignable to Source: + // *Storage_Mysql + // *Storage_Redis + // *Storage_Ldap + Source isStorage_Source `protobuf_oneof:"source"` +} + +func (x *Storage) Reset() { + *x = Storage{} + if protoimpl.UnsafeEnabled { + mi := &file_config_config_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Storage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Storage) ProtoMessage() {} + +func (x *Storage) ProtoReflect() protoreflect.Message { + mi := &file_config_config_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Storage.ProtoReflect.Descriptor instead. +func (*Storage) Descriptor() ([]byte, []int) { + return file_config_config_proto_rawDescGZIP(), []int{3} +} + +func (x *Storage) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (m *Storage) GetSource() isStorage_Source { + if m != nil { + return m.Source + } + return nil +} + +func (x *Storage) GetMysql() *MySQLOptions { + if x, ok := x.GetSource().(*Storage_Mysql); ok { + return x.Mysql + } + return nil +} + +func (x *Storage) GetRedis() *RedisOptions { + if x, ok := x.GetSource().(*Storage_Redis); ok { + return x.Redis + } + return nil +} + +func (x *Storage) GetLdap() *LdapOptions { + if x, ok := x.GetSource().(*Storage_Ldap); ok { + return x.Ldap + } + return nil +} + +type isStorage_Source interface { + isStorage_Source() +} + +type Storage_Mysql struct { + Mysql *MySQLOptions `protobuf:"bytes,11,opt,name=mysql,proto3,oneof"` // MySQL Database Config +} + +type Storage_Redis struct { + Redis *RedisOptions `protobuf:"bytes,12,opt,name=redis,proto3,oneof"` // Redis Database Config +} + +type Storage_Ldap struct { + Ldap *LdapOptions `protobuf:"bytes,13,opt,name=ldap,proto3,oneof"` // LdapDatabaseConfig +} + +func (*Storage_Mysql) isStorage_Source() {} + +func (*Storage_Redis) isStorage_Source() {} + +func (*Storage_Ldap) isStorage_Source() {} + +type Storages struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + User []*Storage `protobuf:"bytes,1,rep,name=user,proto3" json:"user,omitempty"` + Session *Storage `protobuf:"bytes,2,opt,name=session,proto3" json:"session,omitempty"` +} + +func (x *Storages) Reset() { + *x = Storages{} + if protoimpl.UnsafeEnabled { + mi := &file_config_config_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Storages) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Storages) ProtoMessage() {} + +func (x *Storages) ProtoReflect() protoreflect.Message { + mi := &file_config_config_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Storages.ProtoReflect.Descriptor instead. +func (*Storages) Descriptor() ([]byte, []int) { + return file_config_config_proto_rawDescGZIP(), []int{4} +} + +func (x *Storages) GetUser() []*Storage { + if x != nil { + return x.User + } + return nil +} + +func (x *Storages) GetSession() *Storage { + if x != nil { + return x.Session + } + return nil +} + +type Config struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Storage *Storages `protobuf:"bytes,1,opt,name=storage,proto3" json:"storage,omitempty"` +} + +func (x *Config) Reset() { + *x = Config{} + if protoimpl.UnsafeEnabled { + mi := &file_config_config_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Config) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Config) ProtoMessage() {} + +func (x *Config) ProtoReflect() protoreflect.Message { + mi := &file_config_config_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Config.ProtoReflect.Descriptor instead. +func (*Config) Descriptor() ([]byte, []int) { + return file_config_config_proto_rawDescGZIP(), []int{5} +} + +func (x *Config) GetStorage() *Storages { + if x != nil { + return x.Storage + } + return nil +} + +var File_config_config_proto protoreflect.FileDescriptor + +var file_config_config_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x69, 0x64, 0x61, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x20, 0x0a, 0x0c, 0x52, 0x65, 0x64, 0x69, 0x73, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x75, 0x72, 0x6c, 0x22, 0x0d, 0x0a, 0x0b, 0x4c, 0x64, 0x61, 0x70, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x22, 0x9f, 0x03, 0x0a, 0x0c, 0x4d, 0x79, 0x53, 0x51, 0x4c, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x61, 0x78, 0x5f, + 0x69, 0x64, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6d, 0x61, 0x78, 0x49, + 0x64, 0x6c, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x5f, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x12, 0x6d, 0x61, 0x78, 0x49, 0x64, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x5f, 0x6f, 0x70, 0x65, + 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x12, 0x6d, 0x61, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x51, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x15, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x4c, 0x69, 0x66, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, + 0x61, 0x72, 0x73, 0x65, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, + 0x72, 0x73, 0x65, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, + 0x69, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x50, + 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0xbd, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x05, 0x6d, 0x79, 0x73, 0x71, 0x6c, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x64, 0x61, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x2e, 0x4d, 0x79, 0x53, 0x51, 0x4c, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, + 0x00, 0x52, 0x05, 0x6d, 0x79, 0x73, 0x71, 0x6c, 0x12, 0x31, 0x0a, 0x05, 0x72, 0x65, 0x64, 0x69, + 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x69, 0x64, 0x61, 0x73, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x52, 0x65, 0x64, 0x69, 0x73, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x48, 0x00, 0x52, 0x05, 0x72, 0x65, 0x64, 0x69, 0x73, 0x12, 0x2e, 0x0a, 0x04, 0x6c, + 0x64, 0x61, 0x70, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x69, 0x64, 0x61, 0x73, + 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x64, 0x61, 0x70, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x04, 0x6c, 0x64, 0x61, 0x70, 0x42, 0x08, 0x0a, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x64, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x73, 0x12, 0x28, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x69, 0x64, 0x61, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x07, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x69, + 0x64, 0x61, 0x73, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x39, 0x0a, 0x06, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2f, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x69, 0x64, 0x61, 0x73, 0x2e, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x73, 0x52, 0x07, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2f, 0x3b, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x50, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_config_config_proto_rawDescOnce sync.Once + file_config_config_proto_rawDescData = file_config_config_proto_rawDesc +) + +func file_config_config_proto_rawDescGZIP() []byte { + file_config_config_proto_rawDescOnce.Do(func() { + file_config_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_config_config_proto_rawDescData) + }) + return file_config_config_proto_rawDescData +} + +var file_config_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_config_config_proto_goTypes = []interface{}{ + (*RedisOptions)(nil), // 0: idas.config.RedisOptions + (*LdapOptions)(nil), // 1: idas.config.LdapOptions + (*MySQLOptions)(nil), // 2: idas.config.MySQLOptions + (*Storage)(nil), // 3: idas.config.Storage + (*Storages)(nil), // 4: idas.config.Storages + (*Config)(nil), // 5: idas.config.Config + (*types.Duration)(nil), // 6: google.protobuf.Duration +} +var file_config_config_proto_depIdxs = []int32{ + 6, // 0: idas.config.MySQLOptions.max_connection_lifeTime:type_name -> google.protobuf.Duration + 2, // 1: idas.config.Storage.mysql:type_name -> idas.config.MySQLOptions + 0, // 2: idas.config.Storage.redis:type_name -> idas.config.RedisOptions + 1, // 3: idas.config.Storage.ldap:type_name -> idas.config.LdapOptions + 3, // 4: idas.config.Storages.user:type_name -> idas.config.Storage + 3, // 5: idas.config.Storages.session:type_name -> idas.config.Storage + 4, // 6: idas.config.Config.storage:type_name -> idas.config.Storages + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name +} + +func init() { file_config_config_proto_init() } + +func file_config_config_proto_init() { + if File_config_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_config_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RedisOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LdapOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MySQLOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Storage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Storages); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_config_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_config_config_proto_msgTypes[3].OneofWrappers = []interface{}{ + (*Storage_Mysql)(nil), + (*Storage_Redis)(nil), + (*Storage_Ldap)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_config_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_config_config_proto_goTypes, + DependencyIndexes: file_config_config_proto_depIdxs, + MessageInfos: file_config_config_proto_msgTypes, + }.Build() + File_config_config_proto = out.File + file_config_config_proto_rawDesc = nil + file_config_config_proto_goTypes = nil + file_config_config_proto_depIdxs = nil +} diff --git a/config/config.proto b/config/config.proto new file mode 100644 index 0000000..8fadca3 --- /dev/null +++ b/config/config.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; +import public "google/protobuf/duration.proto"; +package idas.config; +option go_package = "./;config"; + +message RedisOptions{ + string url = 1; +} +message LdapOptions{ + +} +message MySQLOptions{ + string host = 1; + string username = 2; + string password = 3; + string schema = 4; + int32 max_idle = 5; + int32 max_idle_connections = 6; + int32 max_open_connections = 7; + google.protobuf.Duration max_connection_lifeTime = 8; + string charset = 9; + string collation = 10; + string table_prefix = 11; +} + + +message Storage{ + string name = 1; + oneof source{ + MySQLOptions mysql = 11; // MySQL Database Config + RedisOptions redis = 12; // Redis Database Config + LdapOptions ldap = 13; // LdapDatabaseConfig + }; +} + +message Storages{ + repeated Storage user = 1; + Storage session = 2; +} + +message Config { + Storages storage = 1; +} \ No newline at end of file diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..ca2a05b --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,68 @@ +package config + +import ( + "bytes" + "fmt" + "testing" + "time" + + "github.com/golang/protobuf/jsonpb" + "github.com/stretchr/testify/require" + + "idas/pkg/logs" + "idas/pkg/utils/httputil" +) + +var conf = ` +storage: + user: + - mysql: + maxIdleConnections: 2 + maxOpenConnections: 100 + maxConnectionLifeTime: 30s + charset: utf8 + collation: utf8_general_ci + tablePrefix: t_idas_ +` + +func TestUnmarshalConfig(t *testing.T) { + logger := logs.New(logs.MustNewConfig("info", "json")) + err := safeCfg.ReloadConfigFromYamlReader(logger, bytes.NewReader([]byte(conf))) + require.NoError(t, err) + require.Equal(t, safeCfg.C.Storage.User[0].GetMysql().TablePrefix, "t_idas_") +} + +func TestMarshalConfig(t *testing.T) { + mysqlOptions := NewMySQLOptions() + mysqlOptions.TablePrefix = "t_xsadfa9i83" + c := Config{ + Storage: &Storages{ + User: []*Storage{{ + Source: &Storage_Mysql{ + Mysql: mysqlOptions, + }, + }}, + }, + } + + marshaler := jsonpb.Marshaler{ + Indent: " ", + OrigName: true, + } + buf := bytes.NewBuffer(nil) + err := marshaler.Marshal(buf, &c) + require.NoError(t, err) + + t.Log(buf.String()) + logger := logs.New(logs.MustNewConfig("info", "json")) + err = safeCfg.ReloadConfigFromYamlReader(logger, bytes.NewReader([]byte(conf))) + require.NoError(t, err) + t.Log(safeCfg.C.Storage) + require.Equal(t, c.Storage.User[0].GetMysql().TablePrefix, "t_xsadfa9i83") + require.NoError(t, err) +} + +func TestValues(t *testing.T) { + ints := httputil.Must[[]time.Duration](httputil.NewValue("10s,2m,60s,30a0m").Split().Durations()) + fmt.Println(ints) +} diff --git a/config/safe.go b/config/safe.go new file mode 100644 index 0000000..da198d7 --- /dev/null +++ b/config/safe.go @@ -0,0 +1,129 @@ +package config + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "sync" + + "github.com/go-kit/log" + "github.com/golang/protobuf/jsonpb" + "github.com/prometheus/client_golang/prometheus" + "gopkg.in/yaml.v3" + + "idas/pkg/global" +) + +var ( + configReloadSuccess = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: global.AppName, + Name: "config_last_reload_successful", + Help: "Blackbox exporter config loaded successfully.", + }) + + configReloadSeconds = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: global.AppName, + Name: "config_last_reload_success_timestamp_seconds", + Help: "Timestamp of the last successful configuration reload.", + }) + safeCfg = newSafeConfig() +) + +type safeConfig struct { + sync.RWMutex + C *Config +} + +func newSafeConfig() *safeConfig { + return &safeConfig{ + C: &Config{}, + } +} + +func Get() *Config { + return safeCfg.GetConfig() +} + +func (sc *safeConfig) SetConfig(conf *Config) { + sc.Lock() + defer sc.Unlock() + sc.C = conf +} + +func (sc *safeConfig) GetConfig() *Config { + sc.RLock() + defer sc.RUnlock() + return sc.C +} + +type Converter map[string]interface{} + +func (c Converter) ToJSON() io.Reader { + jsonReader := bytes.NewBuffer(nil) + if err := json.NewEncoder(jsonReader).Encode(c); err != nil { + panic(err) + } + return jsonReader +} + +func (sc *safeConfig) ReloadConfigFromYamlReader(logger log.Logger, yamlReader io.Reader) (err error) { + defer func() { + if err != nil { + configReloadSuccess.Set(0) + } else { + configReloadSuccess.Set(1) + configReloadSeconds.SetToCurrentTime() + } + }() + cfgConvert := new(Converter) + if err = yaml.NewDecoder(yamlReader).Decode(&cfgConvert); err != nil { + return fmt.Errorf("error parse config file: %s", err) + } + return sc.ReloadConfigFromJSONReader(logger, cfgConvert.ToJSON()) +} + +func (sc *safeConfig) ReloadConfigFromJSONReader(logger log.Logger, reader io.Reader) (err error) { + defer func() { + if err != nil { + configReloadSuccess.Set(0) + } else { + configReloadSuccess.Set(1) + configReloadSeconds.SetToCurrentTime() + } + }() + + var c Config + + var unmarshaler jsonpb.Unmarshaler + if err = unmarshaler.Unmarshal(reader, &c); err != nil { + return fmt.Errorf("error unmarshal config: %s", err) + } else if err = c.Init(logger); err != nil { + return fmt.Errorf("error init config: %s", err) + } + sc.SetConfig(&c) + return nil +} + +func (sc *safeConfig) ReloadConfigFromFile(logger log.Logger, filename string) error { + r, err := os.Open(filename) + if err != nil { + return fmt.Errorf("failed to open file: %s", err) + } + ext := filepath.Ext(filename) + if len(ext) > 1 { + switch ext { + case ".yml", ".yaml": + return sc.ReloadConfigFromYamlReader(logger, r) + case ".json": + return sc.ReloadConfigFromJSONReader(logger, r) + } + } + return nil +} + +func ReloadConfigFromFile(logger log.Logger, filename string) error { + return safeCfg.ReloadConfigFromFile(logger, filename) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cf80c89 --- /dev/null +++ b/go.mod @@ -0,0 +1,74 @@ +module idas + +go 1.18 + +require ( + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d + github.com/emicklei/go-restful/v3 v3.7.4 + github.com/go-kit/kit v0.12.0 + github.com/go-logfmt/logfmt v0.5.1 + github.com/go-redis/redis v6.15.9+incompatible + github.com/gogo/protobuf v1.3.2 + github.com/golang/protobuf v1.5.2 + github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef + github.com/lightstep/lightstep-tracer-go v0.25.0 + github.com/oklog/oklog v0.3.2 + github.com/opentracing/opentracing-go v1.2.0 + github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 + github.com/openzipkin/zipkin-go v0.2.5 + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.11.0 + github.com/satori/go.uuid v1.2.0 + github.com/sony/gobreaker v0.4.1 + github.com/spf13/cobra v1.4.0 + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.7.0 + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac + google.golang.org/protobuf v1.27.1 + gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + gorm.io/driver/mysql v1.3.2 + gorm.io/gorm v1.23.3 + k8s.io/apimachinery v0.23.5 + sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600 +) + +require ( + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/go-kit/log v0.2.0 // indirect + github.com/go-ole/go-ole v1.2.4 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/google/go-cmp v0.5.6 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.4 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect + github.com/opentracing/basictracer-go v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.30.0 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/shirou/gopsutil/v3 v3.21.2 // indirect + github.com/smartystreets/goconvey v1.7.2 // indirect + github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e // indirect + github.com/tklauser/go-sysconf v0.3.4 // indirect + github.com/tklauser/numcpus v0.2.1 // indirect + golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 // indirect + golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect + golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect + google.golang.org/grpc v1.43.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..eb1a3fe --- /dev/null +++ b/go.sum @@ -0,0 +1,728 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.7.4 h1:PVGUqKvvMzQYiO89TdXrH9EMks+okTaRIMQ3jgMdZ30= +github.com/emicklei/go-restful/v3 v3.7.4/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= +github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= +github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 h1:YjW+hUb8Fh2S58z4av4t/0cBMK/Q0aP48RocCFsC8yI= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7/go.mod h1:Spd59icnvRxSKuyijbbwe5AemzvcyXAUBgApa7VybMw= +github.com/lightstep/lightstep-tracer-go v0.25.0 h1:sGVnz8h3jTQuHKMbUe2949nXm3Sg09N1UcR3VoQNN5E= +github.com/lightstep/lightstep-tracer-go v0.25.0/go.mod h1:G1ZAEaqTHFPWpWunnbUn1ADEY/Jvzz7jIOaXwAfD6A8= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0= +github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5 h1:ZCnq+JUrvXcDVhX/xRolRBZifmabN1HcS1wrPSvxhrU= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.5 h1:UwtQQx2pyPIgWYHRg+epgdx1/HnBQTgN3/oIYEJTQzU= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shirou/gopsutil/v3 v3.21.2 h1:fIOk3hyqV1oGKogfGNjUZa0lUbtlkx3+ZT0IoJth2uM= +github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= +github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAHVdPR3IjfmN8T1h2iczJLynhLybf8= +github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tklauser/go-sysconf v0.3.4 h1:HT8SVixZd3IzLdfs/xlpq0jeSfTX57g1v6wB1EuzV7M= +github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= +github.com/tklauser/numcpus v0.2.1 h1:ct88eFm+Q7m2ZfXJdan1xYoXKlmwsfP+k88q05KvlZc= +github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 h1:3erb+vDS8lU1sxfDHF4/hhWyaXnhIaO+7RgL4fDZORA= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +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/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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= +google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I= +gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= +gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.3 h1:jYh3nm7uLZkrMVfA8WVNjDZryKfr7W+HTlInVgKFJAg= +gorm.io/gorm v1.23.3/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/apimachinery v0.23.5 h1:Va7dwhp8wgkUPWsEXk6XglXWU4IKYLKNlv8VkX7SDM0= +k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600 h1:hfyJ5ku9yFtLVOiSxa3IN+dx5eBQT9mPmKFypAmg8XM= +sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/pkg/client/mysql/logger.go b/pkg/client/mysql/logger.go new file mode 100644 index 0000000..ea5be0a --- /dev/null +++ b/pkg/client/mysql/logger.go @@ -0,0 +1,69 @@ +package mysql + +import ( + "context" + "fmt" + "time" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "gorm.io/gorm/utils" +) + +type logContext struct { + logger log.Logger + SlowThreshold time.Duration +} + +func (l *logContext) LogMode(lvl logger.LogLevel) logger.Interface { + var filter log.Logger + switch lvl { + case logger.Silent: + filter = level.NewFilter(l.logger, level.AllowNone()) + case logger.Info: + filter = level.NewFilter(l.logger, level.AllowInfo()) + case logger.Warn: + filter = level.NewFilter(l.logger, level.AllowWarn()) + case logger.Error: + filter = level.NewFilter(l.logger, level.AllowError()) + default: + filter = l.logger + } + + return NewLogAdapter(filter) +} + +func (l logContext) Info(ctx context.Context, msg string, data ...interface{}) { + level.Info(l.logger).Log("caller", utils.FileWithLineNum(), "msg", fmt.Sprintf(msg, data...)) +} + +func (l logContext) Warn(ctx context.Context, msg string, data ...interface{}) { + level.Warn(l.logger).Log("caller", utils.FileWithLineNum(), "msg", fmt.Sprintf(msg, data...)) +} + +func (l logContext) Error(ctx context.Context, msg string, data ...interface{}) { + level.Error(l.logger).Log("caller", utils.FileWithLineNum(), "msg", fmt.Sprintf(msg, data...)) +} + +func (l logContext) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { + elapsed := time.Since(begin) + switch { + case err != nil && err != gorm.ErrRecordNotFound: + sql, rows := fc() + level.Error(l.logger).Log("caller", utils.FileWithLineNum(), "msg", "SQL execution exception", "[ErrorMsg]", err, "[sql]", sql, "[ExecTime]", float64(elapsed.Nanoseconds())/1e6, "[RowReturnCount]", rows) + case elapsed > l.SlowThreshold && l.SlowThreshold != 0: + sql, rows := fc() + level.Warn(l.logger).Log("caller", utils.FileWithLineNum(), "msg", "exec SQL query", "[sql]", sql, "[ExecTime]", float64(elapsed.Nanoseconds())/1e6, "[RowReturnCount]", rows) + default: + sql, rows := fc() + level.Debug(l.logger).Log("caller", utils.FileWithLineNum(), "msg", "exec SQL query", "[sql]", sql, "[ExecTime]", float64(elapsed.Nanoseconds())/1e6, "[RowReturnCount]", rows) + } +} + +func NewLogAdapter(l log.Logger) logger.Interface { + return &logContext{logger: l} +} + +var _ logger.Interface = new(logContext) diff --git a/pkg/client/mysql/mysql.go b/pkg/client/mysql/mysql.go new file mode 100644 index 0000000..79209c8 --- /dev/null +++ b/pkg/client/mysql/mysql.go @@ -0,0 +1,95 @@ +package mysql + +import ( + "context" + "fmt" + + "github.com/go-kit/log/level" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/schema" + + "idas/config" + "idas/pkg/global" + "idas/pkg/logs" + "idas/pkg/utils/signals" +) + +type Database struct { + *gorm.DB +} + +type Client struct { + database *Database +} + +func (c *Client) Session(ctx context.Context) *Database { + logger := logs.GetContextLogger(ctx) + session := &gorm.Session{Logger: NewLogAdapter(logger)} + if conn := ctx.Value(global.MySQLConnName); conn != nil { + switch db := conn.(type) { + case *Database: + return &Database{DB: db.Session(session)} + case *gorm.DB: + return &Database{DB: db.Session(session)} + default: + level.Warn(logger).Log("msg", "未知的上下文属性(global.MySQLConnName)值", global.MySQLConnName, fmt.Sprintf("%#v", conn)) + } + } + return &Database{DB: c.database.Session(session)} +} + +func NewMySQLClient(ctx context.Context, options *config.MySQLOptions) (*Client, error) { + var m Client + logger := logs.GetContextLogger(ctx) + db, err := gorm.Open( + mysql.New(mysql.Config{ + DSN: fmt.Sprintf( + "%s:%s@tcp(%s)/%s?parseTime=1&multiStatements=1&charset=%s&collation=%s", + options.Username, + options.Password, + options.Host, + options.Schema, + options.Charset, + options.Collation, + ), + }), &gorm.Config{ + NamingStrategy: schema.NamingStrategy{ + TablePrefix: "t_", + SingularTable: true, + }, + Logger: NewLogAdapter(logger), + }, + ) + if err != nil { + return nil, fmt.Errorf("连接MySQL数据库[%s@%s]失败(%s)", options.Username, options.Host, err) + } + + { + sqlDB, err := db.DB() + if err != nil { + return nil, fmt.Errorf("连接MySQL数据库[%s@%s]失败(%s)", options.Username, options.Host, err) + } + sqlDB.SetMaxIdleConns(int(options.MaxIdleConnections)) + sqlDB.SetConnMaxLifetime(options.GetStdMaxConnectionLifeTime()) + sqlDB.SetMaxOpenConns(int(options.MaxOpenConnections)) + } + + stopCh := signals.SetupSignalHandler(logger) + stopCh.Add(1) + go func() { + <-stopCh.Channel() + stopCh.WaitRequest() + if sqlDB, err := db.DB(); err == nil { + if err = sqlDB.Close(); err != nil { + level.Warn(logger).Log("msg", "关闭MySQL数据库连接失败", "err", err) + } + } + stopCh.Done() + }() + + m.database = &Database{ + db, + } + return &m, nil +} diff --git a/pkg/client/mysql/options.go b/pkg/client/mysql/options.go new file mode 100644 index 0000000..bd06063 --- /dev/null +++ b/pkg/client/mysql/options.go @@ -0,0 +1,20 @@ +package mysql + +import ( + "time" + + "github.com/gogo/protobuf/types" + + "idas/config" +) + +func NewMySQLOptions() *config.MySQLOptions { + return &config.MySQLOptions{ + Charset: "utf8", + Collation: "utf8_general_ci", + MaxIdleConnections: 2, + MaxOpenConnections: 100, + MaxConnectionLifeTime: types.DurationProto(30 * time.Second), + TablePrefix: "t_", + } +} diff --git a/pkg/client/redis/redis.go b/pkg/client/redis/redis.go new file mode 100644 index 0000000..98e6616 --- /dev/null +++ b/pkg/client/redis/redis.go @@ -0,0 +1,63 @@ +package redis + +import ( + "context" + "time" + + "github.com/go-kit/log/level" + "github.com/go-redis/redis" + + "idas/config" + "idas/pkg/logs" + "idas/pkg/utils/signals" +) + +type Client struct { + client *redis.Client +} + +func NewRedisClientOrDie(ctx context.Context, options *config.RedisOptions) *Client { + client, err := NewRedisClient(ctx, options) + if err != nil { + panic(any(err)) + } + + return client +} + +func NewRedisClient(ctx context.Context, option *config.RedisOptions) (*Client, error) { + var r Client + logger := logs.GetContextLogger(ctx) + options, err := redis.ParseURL(option.Url) + if err != nil { + level.Error(logger).Log("msg", "解析Redis URL失败", "err", err) + return nil, err + } + + r.client = redis.NewClient(options) + if err = r.client.Ping().Err(); err != nil { + level.Error(logger).Log("msg", "Redis连接失败", "err", err) + _ = r.client.Close() + return nil, err + } + + stopCh := signals.SetupSignalHandler(logger) + if stopCh != nil { + stopCh.Add(1) + go func() { + <-stopCh.Channel() + stopCh.WaitRequest() + if err = r.client.Close(); err != nil { + level.Error(logger).Log("msg", "Redis客户端关闭出错", "err", err) + time.Sleep(1 * time.Second) + } + level.Error(logger).Log("msg", "关闭Redis连接", "err", err) + stopCh.Done() + }() + } + return &r, nil +} + +func (r *Client) Redis(ctx context.Context) *redis.Client { + return r.client.WithContext(ctx) +} diff --git a/pkg/endpoint/common.go b/pkg/endpoint/common.go new file mode 100644 index 0000000..c118a39 --- /dev/null +++ b/pkg/endpoint/common.go @@ -0,0 +1,119 @@ +package endpoint + +import ( + "net/http" + + "github.com/emicklei/go-restful/v3" + + "idas/pkg/errors" +) + +type ResponseCode int + +const ( + ResponseCodeUnknown ResponseCode = -99 + ResponseCodeOk ResponseCode = 0 + ResponseCodeNotLogin ResponseCode = 99 + ResponseCodeParameterError ResponseCode = 400 + ResponseCodeForbidden ResponseCode = 403 + ResponseCodeServerError ResponseCode = 500 + ResponseCodeNotFound ResponseCode = 404 +) + +type Lister interface { + GetPageSize() int64 + GetCurrent() int64 + GetTotal() int64 + GetData() interface{} +} +type Total interface { + GetTotal() int64 +} + +type RestfulRequester interface { + GetRestfulRequest() *restful.Request + SetRestfulRequest(r *restful.Request) + GetRestfulResponse() *restful.Response + SetRestfulResponse(c *restful.Response) +} + +var _ RestfulRequester = &BaseRequest{} + +type BaseRequest struct { + restfulRequest *restful.Request + restfulResponse *restful.Response +} + +func (b BaseRequest) GetRestfulRequest() *restful.Request { + return b.restfulRequest +} + +func (b *BaseRequest) SetRestfulRequest(r *restful.Request) { + b.restfulRequest = r +} + +func (b BaseRequest) GetRestfulResponse() *restful.Response { + return b.restfulResponse +} + +func (b *BaseRequest) SetRestfulResponse(r *restful.Response) { + b.restfulResponse = r +} + +type BaseListRequest struct { + BaseRequest + PageSize int64 `json:"pageSize"` + Current int64 `json:"current"` + Keywords string `json:"keywords"` +} +type BaseResponse struct { + Error error `json:"-"` + ErrorMessage string `json:"errorMessage"` +} + +func (l BaseResponse) Failed() error { + if len(l.ErrorMessage) != 0 { + return errors.NewServerError(http.StatusOK, l.ErrorMessage) + } + return l.Error +} + +type BaseListResponse struct { + BaseResponse + Current int64 `json:"current,omitempty"` + PageSize int64 `json:"pageSize,omitempty"` + Total int64 `json:"total,omitempty"` + Data interface{} `json:"data,omitempty"` +} + +func (b BaseListResponse) GetPageSize() int64 { + return b.PageSize +} + +func (b BaseListResponse) GetCurrent() int64 { + return b.Current +} + +func (b BaseListResponse) GetTotal() int64 { + return b.Total +} + +func (b BaseListResponse) GetData() interface{} { + return b.Data +} + +func NewBaseListResponse(req BaseListRequest) BaseListResponse { + return BaseListResponse{ + PageSize: req.PageSize, + Current: req.Current, + } +} + +type BaseTotalResponse struct { + BaseResponse + Total int64 `json:"total,omitempty"` +} + +func (b BaseTotalResponse) GetTotal() int64 { + return b.Total +} diff --git a/pkg/endpoint/middleware.go b/pkg/endpoint/middleware.go new file mode 100644 index 0000000..205f294 --- /dev/null +++ b/pkg/endpoint/middleware.go @@ -0,0 +1,42 @@ +package endpoint + +import ( + "context" + "fmt" + "time" + + "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/metrics" + "github.com/go-kit/log/level" + + "idas/pkg/logs" +) + +// InstrumentingMiddleware returns an endpoint middleware that records +// the duration of each invocation to the passed histogram. The middleware adds +// a single field: "success", which is "true" if no error is returned, and +// "false" otherwise. +func InstrumentingMiddleware(duration metrics.Histogram) endpoint.Middleware { + return func(next endpoint.Endpoint) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + defer func(begin time.Time) { + duration.With("success", fmt.Sprint(err == nil)).Observe(time.Since(begin).Seconds()) + }(time.Now()) + return next(ctx, request) + } + } +} + +// LoggingMiddleware returns an endpoint middleware that logs the +// duration of each invocation, and the resulting error, if any. +func LoggingMiddleware(method string) endpoint.Middleware { + return func(next endpoint.Endpoint) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + logger := logs.GetContextLogger(ctx) + defer func(begin time.Time) { + level.Debug(logger).Log("transport_error", err, "method", method, "took", time.Since(begin)) + }(time.Now()) + return next(ctx, request) + } + } +} diff --git a/pkg/endpoint/oauth.go b/pkg/endpoint/oauth.go new file mode 100644 index 0000000..46068a2 --- /dev/null +++ b/pkg/endpoint/oauth.go @@ -0,0 +1,108 @@ +package endpoint + +import ( + "context" + "fmt" + + "github.com/go-kit/kit/endpoint" + + "idas/pkg/service" +) + +type OAuthGrantType string + +const ( + OAuthTypeRefreshToken OAuthGrantType = "refresh_token" + OAuthTypeAuthorizationCode OAuthGrantType = "authorization_code" + OAuthTypePassword OAuthGrantType = "password" + OAuthTypeClientCredentials OAuthGrantType = "client_credentials" +) + +type OAuthTokenRequest struct { + BaseRequest + Code string `json:"code"` + GrantType OAuthGrantType `json:"grant_type"` + RedirectURI string `json:"redirect_uri"` + ClientId string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Password string `json:"password"` + Username string `json:"username"` + RefreshToken string `json:"refresh_token"` +} + +type OAuthTokenResponse struct { + Error string `json:"error"` + AccessToken string `json:"access_token,omitempty"` + TokenType string `json:"token_type,omitempty"` + ExpiresIn int `json:"expires_in,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` +} + +func MakeOAuthTokensEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*OAuthTokenRequest) + resp := OAuthTokenResponse{TokenType: "Bearer"} + if restfulReq := req.GetRestfulRequest(); restfulReq != nil { + err = fmt.Errorf("invalid_grant") + } else { + switch req.GrantType { + case OAuthTypeAuthorizationCode: + resp.AccessToken, resp.RefreshToken, resp.ExpiresIn, err = s.GetOAuthTokenByAuthorizationCode(ctx, req.Code, req.ClientId, req.RedirectURI) + case OAuthTypePassword: + resp.AccessToken, resp.RefreshToken, resp.ExpiresIn, err = s.GetOAuthTokenByPassword(ctx, req.Username, req.Password) + case OAuthTypeClientCredentials: + if username, password, ok := restfulReq.Request.BasicAuth(); ok { + resp.AccessToken, resp.RefreshToken, resp.ExpiresIn, err = s.GetOAuthTokenByPassword(ctx, username, password) + } else { + err = fmt.Errorf("invalid_request") + } + case OAuthTypeRefreshToken: + if username, password, ok := restfulReq.Request.BasicAuth(); ok { + resp.AccessToken, resp.RefreshToken, resp.ExpiresIn, err = s.RefreshOAuthTokenByPassword(ctx, req.RefreshToken, username, password) + } else if len(req.Username) != 0 && len(req.Password) != 0 { + resp.AccessToken, resp.RefreshToken, resp.ExpiresIn, err = s.RefreshOAuthTokenByPassword(ctx, req.RefreshToken, req.Username, req.Password) + } else { + resp.AccessToken, resp.RefreshToken, resp.ExpiresIn, err = s.RefreshOAuthTokenByAuthorizationCode(ctx, req.RefreshToken, req.ClientId, req.ClientSecret) + } + default: + err = fmt.Errorf("unsupported_grant_type") + } + } + + if err != nil { + resp.Error = err.Error() + if restfulResp := req.GetRestfulResponse(); restfulResp != nil { + restfulResp.WriteHeader(400) + } + } + return &resp, nil + } +} + +type OAuthAuthorizeRequest struct { + BaseRequest + ResponseType string `json:"response_type"` + ClientId string `json:"client_id"` + RedirectURI string `json:"redirect_uri"` +} + +type OAuthAuthorizeResponse struct { + BaseResponse `json:",inline"` +} + +func MakeOAuthAuthorizeEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*OAuthAuthorizeRequest) + resp := OAuthAuthorizeResponse{} + var redirect string + if redirect, err = s.OAuthAuthorize(ctx, req.ResponseType, req.ClientId, req.RedirectURI); err == nil { + if req.GetRestfulResponse() != nil && len(redirect) > 0 { + req.GetRestfulResponse().AddHeader("Location", redirect) + req.GetRestfulResponse().ResponseWriter.WriteHeader(302) + } + } else { + resp.Error = err + } + return &resp, nil + } +} diff --git a/pkg/endpoint/set.go b/pkg/endpoint/set.go new file mode 100644 index 0000000..5d99136 --- /dev/null +++ b/pkg/endpoint/set.go @@ -0,0 +1,87 @@ +package endpoint + +import ( + "github.com/go-kit/kit/circuitbreaker" + "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/ratelimit" + "github.com/go-kit/kit/tracing/opentracing" + "github.com/go-kit/kit/tracing/zipkin" + "github.com/go-kit/log" + stdopentracing "github.com/opentracing/opentracing-go" + stdzipkin "github.com/openzipkin/zipkin-go" + "github.com/sony/gobreaker" + "golang.org/x/time/rate" + + "idas/pkg/service" +) + +type UserEndpoints struct { + GetUsers, + PatchUsers, + DeleteUsers, + UpdateUser, + GetUserInfo, + CreateUser, + PatchUser, + DeleteUser, + GetUserSource, + CurrentUser endpoint.Endpoint +} + +type AppEndpoints struct{} + +type AuthEndpoints struct { + UserLogin, + UserLogout, + GetLoginSession, + OAuthTokens, + OAuthAuthorize endpoint.Endpoint +} + +// Set collects all of the endpoints that compose an add service. It's meant to +// be used as a helper struct, to collect all of the endpoints into a single +// parameter. +type Set struct { + UserEndpoints + AuthEndpoints + AppEndpoints +} + +// New returns a Set that wraps the provided server, and wires in all of the +// expected endpoint middlewares via the various parameters. +func New(svc service.Service, logger log.Logger, duration metrics.Histogram, otTracer stdopentracing.Tracer, zipkinTracer *stdzipkin.Tracer) Set { + return Set{ + UserEndpoints: UserEndpoints{ + CurrentUser: InjectEndpoint(logger, "CurrentUser", duration, otTracer, zipkinTracer, MakeCurrentUserEndpoint(svc)), + GetUsers: InjectEndpoint(logger, "GetUsers", duration, otTracer, zipkinTracer, MakeGetUsersEndpoint(svc)), + DeleteUsers: InjectEndpoint(logger, "DeleteUsers", duration, otTracer, zipkinTracer, MakeDeleteUsersEndpoint(svc)), + PatchUsers: InjectEndpoint(logger, "PatchUsers", duration, otTracer, zipkinTracer, MakePatchUsersEndpoint(svc)), + UpdateUser: InjectEndpoint(logger, "UpdateUser", duration, otTracer, zipkinTracer, MakeUpdateUserEndpoint(svc)), + GetUserInfo: InjectEndpoint(logger, "GetUserInfo", duration, otTracer, zipkinTracer, MakeGetUserInfoEndpoint(svc)), + CreateUser: InjectEndpoint(logger, "CreateUser", duration, otTracer, zipkinTracer, MakeCreateUserEndpoint(svc)), + PatchUser: InjectEndpoint(logger, "PatchUser", duration, otTracer, zipkinTracer, MakePatchUserEndpoint(svc)), + DeleteUser: InjectEndpoint(logger, "DeleteUser", duration, otTracer, zipkinTracer, MakeDeleteUserEndpoint(svc)), + GetUserSource: InjectEndpoint(logger, "DeleteUser", duration, otTracer, zipkinTracer, MakeGetUserSourceRequestEndpoint(svc)), + }, + AuthEndpoints: AuthEndpoints{ + UserLogin: InjectEndpoint(logger, "UserLogin", duration, otTracer, zipkinTracer, MakeUserLoginEndpoint(svc)), + UserLogout: InjectEndpoint(logger, "UserLogout", duration, otTracer, zipkinTracer, MakeUserLogoutEndpoint(svc)), + GetLoginSession: InjectEndpoint(logger, "GetLoginSession", duration, otTracer, zipkinTracer, MakeGetLoginSessionEndpoint(svc)), + OAuthTokens: InjectEndpoint(logger, "OAuthTokens", duration, otTracer, zipkinTracer, MakeOAuthTokensEndpoint(svc)), + OAuthAuthorize: InjectEndpoint(logger, "OAuthAuthorize", duration, otTracer, zipkinTracer, MakeOAuthAuthorizeEndpoint(svc)), + }, + } +} + +func InjectEndpoint(logger log.Logger, name string, duration metrics.Histogram, tracer stdopentracing.Tracer, zipkinTracer *stdzipkin.Tracer, ep endpoint.Endpoint) endpoint.Endpoint { + ep = ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Limit(1), 100))(ep) + ep = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(ep) + ep = opentracing.TraceServer(tracer, name)(ep) + if zipkinTracer != nil { + ep = zipkin.TraceEndpoint(zipkinTracer, name)(ep) + } + ep = LoggingMiddleware(name)(ep) + ep = InstrumentingMiddleware(duration.With("method", "Concat"))(ep) + return ep +} diff --git a/pkg/endpoint/user.go b/pkg/endpoint/user.go new file mode 100644 index 0000000..96671b8 --- /dev/null +++ b/pkg/endpoint/user.go @@ -0,0 +1,297 @@ +package endpoint + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/go-kit/kit/endpoint" + + "idas/pkg/errors" + "idas/pkg/global" + "idas/pkg/service" + "idas/pkg/service/models" +) + +type UserLoginRequest struct { + BaseRequest + Username string `json:"username,omitempty"` + Password string `json:"password"` + RememberMe bool `json:"rememberMe"` +} + +type UserLoginResponse struct { + BaseResponse `json:"-"` +} + +func MakeUserLoginEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*UserLoginRequest) + resp := UserLoginResponse{} + if loginCookie, err := s.CreateLoginSession(ctx, req.Username, req.Password); err == nil { + req.restfulResponse.AddHeader("Set-Cookie", loginCookie) + } else { + resp.Error = errors.NewServerError(http.StatusUnauthorized, "Wrong user name or password") + } + return &resp, nil + } +} + +type UserLogoutRequest struct { + BaseRequest +} + +type UserLogoutResponse struct { + BaseResponse `json:",inline"` +} + +func MakeUserLogoutEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*UserLogoutRequest) + resp := UserLogoutResponse{} + cookie, err := req.GetRestfulRequest().Request.Cookie(global.LoginSession) + if err != nil { + resp.Error = errors.BadRequestError + } else if len(cookie.Value) > 0 { + if loginCookie, err := s.DeleteLoginSession(ctx, cookie.Value); err == nil { + req.restfulResponse.AddHeader("Set-Cookie", fmt.Sprintf(loginCookie)) + } else { + resp.Error = errors.InternalServerError + } + } else { + resp.Error = errors.NewServerError(http.StatusUnauthorized, "Invalid identity information") + } + return &resp, nil + } +} + +type CurrentUserRequest struct { + BaseRequest +} + +type CurrentUserResponse struct { + BaseResponse `json:"-"` + *models.User +} + +func MakeCurrentUserEndpoint(_ service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*CurrentUserRequest) + resp := CurrentUserResponse{} + if user, ok := req.GetRestfulRequest().Attribute(global.AttrUser).(*models.User); ok { + resp.User = user + } else { + resp.Error = errors.NotLoginError + } + return &resp, nil + } +} + +type GetUsersRequest struct { + BaseListRequest + Status models.UserStatus `json:"status"` + Storage string `json:"storage"` +} + +type GetUsersResponse struct { + BaseListResponse `json:"-"` +} + +func MakeGetUsersEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*GetUsersRequest) + resp := GetUsersResponse{BaseListResponse: NewBaseListResponse(req.BaseListRequest)} + resp.Data, resp.Total, resp.Error = s.GetUsers(ctx, req.Storage, req.Keywords, req.Status, req.Current, req.PageSize) + return &resp, nil + } +} + +type GetUserSourceRequest struct { + BaseRequest +} + +type GetUserSourceResponse struct { + BaseListResponse `json:"-"` +} + +func MakeGetUserSourceRequestEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + resp := GetUserSourceResponse{} + resp.Data, resp.Total, resp.Error = s.GetUserSource(ctx) + return &resp, nil + } +} + +type PatchUsersRequest struct { + BaseRequest + userPatch []map[string]interface{} + Storage string `json:"storage" valid:"required"` +} + +func (p *PatchUsersRequest) UnmarshalJSON(bytes []byte) error { + return json.Unmarshal(bytes, &p.userPatch) +} + +type PatchUsersResponse struct { + BaseTotalResponse `json:"-"` +} + +func MakePatchUsersEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*PatchUsersRequest) + resp := PatchUsersResponse{} + resp.Total, resp.ErrorMessage, resp.Error = s.PatchUsers(ctx, req.Storage, req.userPatch) + return &resp, nil + } +} + +type DeleteUsersRequest struct { + BaseRequest + Id []string `valid:"required,notnull"` + Storage string `json:"storage" valid:"required"` +} + +type DeleteUsersResponse struct { + BaseTotalResponse `json:"-"` +} + +func MakeDeleteUsersEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*DeleteUsersRequest) + resp := DeleteUsersResponse{} + resp.Total, resp.ErrorMessage, resp.Error = s.DeleteUsers(ctx, req.Storage, req.Id) + return &resp, nil + } +} + +type UpdateUserRequest struct { + BaseRequest + User *models.User `json:",inline"` + Storage string `json:"storage" valid:"required"` +} + +type UpdateUserResponse struct { + BaseResponse `json:"-"` + User *models.User `json:",inline"` +} + +func MakeUpdateUserEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*UpdateUserRequest) + resp := UpdateUserResponse{} + if resp.User, resp.ErrorMessage, resp.Error = s.UpdateUser(ctx, req.Storage, req.User); resp.Error != nil { + resp.Error = errors.NewServerError(200, resp.Error.Error()) + } + return &resp, nil + } +} + +type GetUserRequest struct { + BaseRequest + Id string + Username string + Storage string `json:"storage" valid:"required"` +} + +type GetUserResponse struct { + BaseResponse `json:"-"` + User *models.User `json:",inline"` +} + +func MakeGetUserInfoEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(GetUserRequest) + resp := GetUserResponse{} + resp.User, resp.ErrorMessage, resp.Error = s.GetUserInfo(ctx, req.Storage, req.Id, req.Username) + return &resp, nil + } +} + +type CreateUserRequest struct { + BaseRequest + User *models.User `json:",inline"` + Storage string `json:"storage" valid:"required"` +} + +type CreateUserResponse struct { + BaseResponse `json:"-"` + User *models.User `json:",inline"` +} + +func MakeCreateUserEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*CreateUserRequest) + resp := CreateUserResponse{} + resp.User, resp.ErrorMessage, resp.Error = s.CreateUser(ctx, req.Storage, req.User) + return &resp, nil + } +} + +type PatchUserRequest struct { + BaseRequest + fields map[string]interface{} + Storage string `json:"storage" valid:"required"` +} + +type PatchUserResponse struct { + BaseResponse `json:"-"` + User *models.User `json:",inline"` +} + +func (p *PatchUserRequest) UnmarshalJSON(data []byte) error { + fields := map[string]interface{}{} + if err := json.Unmarshal(data, &fields); err != nil { + return err + } + p.fields = fields + return nil +} + +func MakePatchUserEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*PatchUserRequest) + resp := PatchUserResponse{} + resp.User, resp.ErrorMessage, resp.Error = s.PatchUser(ctx, req.Storage, req.fields) + return &resp, nil + } +} + +type DeleteUserRequest struct { + BaseRequest + Id string `valid:"required"` + Storage string `json:"storage" valid:"required"` +} + +type DeleteUserResponse struct { + BaseResponse `json:"-"` +} + +func MakeDeleteUserEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(*DeleteUserRequest) + resp := DeleteUserResponse{} + resp.ErrorMessage, resp.Error = s.DeleteUser(ctx, req.Storage, req.Id) + return &resp, nil + } +} + +type GetLoginSession struct { + BaseResponse `json:"-"` + User *models.User `json:",inline"` +} + +func MakeGetLoginSessionEndpoint(s service.Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + sessionId := request.(string) + var resp *models.User + if sessionId != "" { + if resp, _, err = s.GetLoginSession(ctx, sessionId); err != nil { + err = errors.NotLoginError + } + } else { + err = errors.NotLoginError + } + return resp, err + } +} diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go new file mode 100644 index 0000000..0b8e1cb --- /dev/null +++ b/pkg/errors/errors.go @@ -0,0 +1,56 @@ +package errors + +import ( + "net/http" + "strconv" +) + +type ServerError interface { + Code() string + StatusCode() int + error +} + +var _ ServerError = &serverError{} + +type serverError struct { + code string + status int + err string +} + +func (s serverError) Code() string { + return s.code +} + +func (s serverError) StatusCode() int { + return s.status +} + +func (s serverError) Error() string { + return s.err +} + +func NewServerError(status int, err string, code ...string) ServerError { + var c string + if len(code) <= 0 { + c = strconv.Itoa(status) + } else { + c = code[0] + } + return &serverError{ + code: c, + status: status, + err: err, + } +} + +var ( + InternalServerError = NewServerError(http.StatusInternalServerError, "Internal server error") + NotLoginError = NewServerError(http.StatusInternalServerError, "Not logged in") + BadRequestError = NewServerError(http.StatusBadRequest, "Invalid Request") + UnauthorizedError = NewServerError(http.StatusUnauthorized, "Invalid identity information") + StatusNotFound = func(name string) ServerError { + return NewServerError(http.StatusNotFound, name+" Not Found") + } +) diff --git a/pkg/global/define.go b/pkg/global/define.go new file mode 100644 index 0000000..318835d --- /dev/null +++ b/pkg/global/define.go @@ -0,0 +1,18 @@ +package global + +import "time" + +const ( + AppName = "idas" + LoginSession = "LOGIN_SESSION" + LoggerName = "__logger__" + TraceIdName = "traceId" + CallerName = "caller" + RestfulRequestContextName = "__restful_request__" + RestfulResponseContextName = "__restful_response__" + MetaNeedLogin = "__need_login__" + MySQLConnName = "__mysql_conn__" + AttrUser = "__user__" + LoginSessionExpiration = 7 * 24 * time.Hour + LoginSessionExpiresFormat = "Mon, 02-Jan-06 15:04:05 MST" +) diff --git a/pkg/logs/flag/flag.go b/pkg/logs/flag/flag.go new file mode 100644 index 0000000..d9f0968 --- /dev/null +++ b/pkg/logs/flag/flag.go @@ -0,0 +1,43 @@ +// Copyright 2017 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package flag + +import ( + "github.com/spf13/pflag" + + "idas/pkg/logs" +) + +// LevelFlagName is the canonical flag name to configure the allowed log level +// within Prometheus projects. +const LevelFlagName = "log.level" + +// LevelFlagHelp is the help description for the log.level flag. +const LevelFlagHelp = "Only log messages with the given severity or above. One of: [debug, info, warn, error]" + +// FormatFlagName is the canonical flag name to configure the log format +// within Prometheus projects. +const FormatFlagName = "log.format" + +// FormatFlagHelp is the help description for the log.format flag. +const FormatFlagHelp = "Output format of log messages. One of: [logfmt, json, idas]" + +// AddFlags adds the flags used by this package to the Kingpin application. +// To use the default Kingpin application, call AddFlags(kingpin.CommandLine) +func AddFlags(set *pflag.FlagSet, config *logs.Config) { + config.Level = new(logs.AllowedLevel) + set.StringVar((*string)(config.Level), LevelFlagName, string(logs.LevelInfo), LevelFlagHelp) + config.Format = new(logs.AllowedFormat) + set.StringVar((*string)(config.Format), FormatFlagName, string(logs.FormatIdas), FormatFlagHelp) +} diff --git a/pkg/logs/log.go b/pkg/logs/log.go new file mode 100644 index 0000000..453c27f --- /dev/null +++ b/pkg/logs/log.go @@ -0,0 +1,289 @@ +// Copyright 2017 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package logs defines standardised ways to initialize Go kit loggers +// across Prometheus components. +// It should typically only ever be imported by main packages. +package logs + +import ( + "context" + "io" + "os" + "strings" + "sync" + "time" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" + + "idas/pkg/global" +) + +// This timestamp format differs from RFC3339Nano by using .000 instead +// of .999999999 which changes the timestamp from 9 variable to 3 fixed +// decimals (.130 instead of .130987456). +var timestampFormat = log.TimestampFormat( + func() time.Time { return time.Now().UTC() }, + "2006-01-02T15:04:05.000Z07:00", +) + +// AllowedLevel is a settable identifier for the minimum level a log entry +// must be have. +type AllowedLevel string + +func (l AllowedLevel) getOption() level.Option { + switch l { + case "debug": + return level.AllowDebug() + case "info": + return level.AllowInfo() + case "warn": + return level.AllowWarn() + case "error": + return level.AllowError() + default: + return level.AllowWarn() + } +} + +func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + type plain string + if err := unmarshal((*plain)(&s)); err != nil { + return err + } + if s == "" { + return nil + } + var lo AllowedLevel + if err := lo.Set(s); err != nil { + return err + } + *l = lo + return nil +} + +func (l AllowedLevel) String() string { + return string(l) +} + +func (l AllowedLevel) Valid() error { + switch l { + case LevelDebug, LevelInfo, LevelWarn, LevelError: + return nil + default: + return errors.Errorf("unrecognized log level %s", l) + } +} + +// Set updates the value of the allowed level. +func (l *AllowedLevel) Set(s string) error { + lvl := AllowedLevel(s) + if err := lvl.Valid(); err != nil { + return err + } + *l = AllowedLevel(s) + return nil +} + +const ( + LevelDebug AllowedLevel = "debug" + LevelInfo AllowedLevel = "info" + LevelWarn AllowedLevel = "warn" + LevelError AllowedLevel = "error" +) + +// AllowedFormat is a settable identifier for the output format that the logger can have. +type AllowedFormat string + +func (f AllowedFormat) String() string { + return string(f) +} + +func (f AllowedFormat) Valid() error { + switch f { + case FormatIdas, FormatLogfmt, FormatJSON: + return nil + default: + return errors.Errorf("unrecognized log format %s", f) + } +} + +// Set updates the value of the allowed format. +func (f *AllowedFormat) Set(s string) error { + format := AllowedFormat(s) + if err := format.Valid(); err != nil { + return err + } + *f = format + return nil +} + +const ( + FormatIdas AllowedFormat = "idas" + FormatJSON AllowedFormat = "json" + FormatLogfmt AllowedFormat = "logfmt" +) + +// Config is a struct containing configurable settings for the logger +type Config struct { + Level *AllowedLevel + Format *AllowedFormat +} + +func MustNewConfig(level string, format string) *Config { + cfg := &Config{Level: new(AllowedLevel), Format: new(AllowedFormat)} + if err := cfg.Level.Set(level); err != nil { + panic(err) + } + if err := cfg.Format.Set(format); err != nil { + panic(err) + } + return cfg +} + +// New returns a new leveled oklog logger. Each logged line will be annotated +// with a timestamp. The output always goes to stderr. +func New(config *Config) log.Logger { + var l log.Logger + switch *config.Format { + case FormatLogfmt: + l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr)) + case FormatJSON: + l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) + default: + l = NewIdasLogger(log.NewSyncWriter(os.Stderr)) + } + + if config.Level != nil { + l = level.NewFilter(l, config.Level.getOption()) + } + l = log.With(l, "ts", timestampFormat, global.CallerName, log.DefaultCaller) + return l +} + +// NewDynamic returns a new leveled logger. Each logged line will be annotated +// with a timestamp. The output always goes to stderr. Some properties can be +// changed, like the level. +func newDynamic(config *Config) *logger { + var l log.Logger + if config.Format != nil && *config.Format == "json" { + l = log.NewJSONLogger(log.NewSyncWriter(os.Stderr)) + } else { + l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) + } + l = log.With(l, "ts", timestampFormat, global.CallerName, log.DefaultCaller) + + lo := &logger{ + base: l, + leveled: l, + } + if config.Level != nil { + lo.SetLevel(config.Level) + } + return lo +} + +type logger struct { + base log.Logger + leveled log.Logger + currentLevel *AllowedLevel + mtx sync.Mutex +} + +// Log implements logger.Log. +func (l *logger) Log(keyvals ...interface{}) error { + l.mtx.Lock() + defer l.mtx.Unlock() + return l.leveled.Log(keyvals...) +} + +// SetLevel changes the log level. +func (l *logger) SetLevel(lvl *AllowedLevel) { + l.mtx.Lock() + defer l.mtx.Unlock() + if lvl != nil { + if l.currentLevel != nil && *l.currentLevel != *lvl { + _ = l.base.Log("msg", "Log level changed", "prev", l.currentLevel, "current", lvl) + } + l.currentLevel = lvl + } + l.leveled = level.NewFilter(l.base, lvl.getOption()) +} + +var rootLogger log.Logger + +func SetRootLogger(logger log.Logger) { + rootLogger = logger +} + +func GetRootLogger() log.Logger { + return rootLogger +} + +func NewTraceLogger() log.Logger { + traceId := strings.ReplaceAll(uuid.NewV4().String(), "-", "") + return log.With(rootLogger, global.TraceIdName, traceId) +} + +func GetContextLogger(ctx context.Context) log.Logger { + if lgr, ok := ctx.Value(global.LoggerName).(log.Logger); ok { + return lgr + } + return NewTraceLogger() +} + +type WriterAdapter struct { + l log.Logger + msgKey string + prefix string + joinPrefixToMsg bool +} + +func (a WriterAdapter) Write(p []byte) (n int, err error) { + a.l.Log(a.msgKey, a.handleMessagePrefix(string(p))) + return len(p), nil +} + +func (a WriterAdapter) handleMessagePrefix(msg string) string { + if a.prefix == "" { + return msg + } + + msg = strings.TrimPrefix(msg, a.prefix) + if a.joinPrefixToMsg { + msg = a.prefix + msg + } + return msg +} + +func MessageKey(key string) WriterAdapterOption { + return func(a *WriterAdapter) { a.msgKey = key } +} + +func Prefix(prefix string, joinPrefixToMsg bool) WriterAdapterOption { + return func(a *WriterAdapter) { a.prefix = prefix; a.joinPrefixToMsg = joinPrefixToMsg } +} + +type WriterAdapterOption func(*WriterAdapter) + +func NewWriterAdapter(logger log.Logger, options ...WriterAdapterOption) io.Writer { + adapter := &WriterAdapter{l: logger, msgKey: "msg"} + for _, option := range options { + option(adapter) + } + return adapter +} diff --git a/pkg/logs/log_test.go b/pkg/logs/log_test.go new file mode 100644 index 0000000..4c9834c --- /dev/null +++ b/pkg/logs/log_test.go @@ -0,0 +1,127 @@ +// Copyright 2020 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logs + +import ( + "fmt" + "testing" + + "github.com/go-kit/log/level" + "gopkg.in/yaml.v2" +) + +// Make sure creating and using a logger with an empty configuration doesn't +// result in a panic. +func TestDefaultConfig(t *testing.T) { + logger := New(&Config{}) + + if err := logger.Log("hello", "world"); err != nil { + t.Fatal(err) + } +} + +func TestUnmarshallLevel(t *testing.T) { + l := new(AllowedLevel) + err := yaml.Unmarshal([]byte(`debug`), l) + if err != nil { + t.Error(err) + } + if *l != "debug" { + t.Errorf("expected %s, got %s", "debug", *l) + } +} + +func TestUnmarshallEmptyLevel(t *testing.T) { + l := new(AllowedLevel) + err := yaml.Unmarshal([]byte(``), l) + if err != nil { + t.Error(err) + } + if *l != "" { + t.Errorf("expected empty level, got %s", *l) + } +} + +func TestUnmarshallBadLevel(t *testing.T) { + l := new(AllowedLevel) + err := yaml.Unmarshal([]byte(`debugg`), l) + if err == nil { + t.Error("expected error") + } + expErr := `unrecognized log level "debugg"` + if err.Error() != expErr { + t.Errorf("expected error %s, got %s", expErr, err.Error()) + } + if *l != "" { + t.Errorf("expected empty level, got %s", *l) + } +} + +type recordKeyvalLogger struct { + count int +} + +func (r *recordKeyvalLogger) Log(keyvals ...interface{}) error { + for _, v := range keyvals { + if fmt.Sprintf("%v", v) == "Log level changed" { + return nil + } + } + r.count++ + return nil +} + +func TestDynamic(t *testing.T) { + logger := newDynamic(&Config{}) + + debugLevel := new(AllowedLevel) + if err := debugLevel.Set("debug"); err != nil { + t.Fatal(err) + } + infoLevel := new(AllowedLevel) + if err := infoLevel.Set("info"); err != nil { + t.Fatal(err) + } + + recorder := &recordKeyvalLogger{} + logger.base = recorder + logger.SetLevel(debugLevel) + if err := level.Debug(logger).Log("hello", "world"); err != nil { + t.Fatal(err) + } + if recorder.count != 1 { + t.Fatal("log not found") + } + + recorder.count = 0 + logger.SetLevel(infoLevel) + if err := level.Debug(logger).Log("hello", "world"); err != nil { + t.Fatal(err) + } + if recorder.count != 0 { + t.Fatal("log found") + } + if err := level.Info(logger).Log("hello", "world"); err != nil { + t.Fatal(err) + } + if recorder.count != 1 { + t.Fatal("log not found") + } + if err := level.Debug(logger).Log("hello", "world"); err != nil { + t.Fatal(err) + } + if recorder.count != 1 { + t.Fatal("extra log found") + } +} diff --git a/pkg/logs/logger.go b/pkg/logs/logger.go new file mode 100644 index 0000000..8cd2c64 --- /dev/null +++ b/pkg/logs/logger.go @@ -0,0 +1,163 @@ +package logs + +import ( + "bytes" + "fmt" + "io" + "runtime" + "strconv" + "strings" + "sync" + + "github.com/go-kit/log" + "github.com/go-logfmt/logfmt" + uuid "github.com/satori/go.uuid" + + "idas/pkg/global" +) + +const titleBg = "---------------------------------------------------------------------------------------\n" + +type logfmtEncoder struct { + *logfmt.Encoder + buf bytes.Buffer +} + +func (l *logfmtEncoder) Reset() { + l.Encoder.Reset() + l.buf.Reset() +} + +var idasEncoderPool = sync.Pool{ + New: func() interface{} { + var enc logfmtEncoder + enc.Encoder = logfmt.NewEncoder(&enc.buf) + return &enc + }, +} + +type logKvPair struct { + key string + val interface{} +} + +type idasLog struct { + level interface{} + ts interface{} + caller interface{} + traceId interface{} + msg interface{} + title string + kvs []interface{} + other []logKvPair + otherKeyMaxLen int +} + +type idasLogger struct { + w io.Writer +} + +func (l *idasLogger) encodeKeyvals(keyvals ...interface{}) ([]byte, error) { + enc := idasEncoderPool.Get().(*logfmtEncoder) + enc.Reset() + defer idasEncoderPool.Put(enc) + + if err := enc.EncodeKeyvals(keyvals...); err != nil { + return nil, err + } + + // Add newline to the end of the buffer + if err := enc.EndRecord(); err != nil { + return nil, err + } + return enc.buf.Bytes(), nil +} + +func (l *idasLogger) Log(keyvals ...interface{}) error { + ll := &idasLog{otherKeyMaxLen: 18, caller: log.DefaultCaller} + for i := 0; ; { + v := keyvals[i+1] + if k, ok := keyvals[i].(string); ok { + if k == "level" { + ll.level = v + } else if k == "ts" { + ll.ts = v + } else if k == global.CallerName { + ll.caller = v + } else if k == "msg" { + ll.msg = v + } else if k == "title" { + ll.title = fmt.Sprintf("%s", v) + } else if k == global.TraceIdName { + ll.traceId = v + } else { + if len(k) > 0 && k[0] == '[' && k[len(k)-1] == ']' { + ll.other = append(ll.other, logKvPair{key: k, val: v}) + if len(k) > ll.otherKeyMaxLen { + ll.otherKeyMaxLen = len(k) + } + } else { + ll.kvs = append(ll.kvs, k, v) + } + } + } else { + ll.kvs = append(ll.kvs, k, v) + } + i += 2 + if i >= len(keyvals) { + break + } + } + if ll.traceId == nil { + ll.traceId = strings.ReplaceAll(uuid.NewV4().String(), "-", "") + } + if ll.level == nil { + ll.level = LevelInfo + } + if ll.caller == nil { + _, file, line, _ := runtime.Caller(5) + ll.caller = file + ":" + strconv.Itoa(line) + } + if ll.ts == nil { + ll.ts = timestampFormat() + } + if ll.msg == nil { + ll.msg = "" + } + buffer := bytes.NewBufferString(fmt.Sprintf("%s [%s] %s %s - %v - ", ll.ts, ll.level, ll.traceId, ll.caller, ll.msg)) + + if data, err := l.encodeKeyvals(ll.kvs...); err != nil { + return err + } else if _, err = buffer.Write(data); err != nil { + return err + } else if len(ll.title) > 0 || len(ll.other) > 0 { + if len(ll.title) > 0 { + if len(ll.title) > len(titleBg) { + buffer.WriteString(ll.title) + } else { + title := []byte(titleBg) + idx := (len(title) - len(ll.title)) / 2 + copy(title[idx:len(ll.title)+idx], []byte(ll.title)) + buffer.Write(title) + } + } + for _, v := range ll.other { + buffer.WriteString(fmt.Sprintf("%-"+strconv.Itoa(ll.otherKeyMaxLen)+"s%v\n", fmt.Sprintf("%s:", v.key), v.val)) + } + if len(ll.title) > 0 { + buffer.WriteString(titleBg) + } + } + if _, err := l.w.Write(buffer.Bytes()); err != nil { + fmt.Printf("格式化异常==>%s(%s)\n", buffer.String(), err) + } + return nil +} + +// NewIdasLogger returns a logger that encodes keyvals to the Writer in +// logfmt format. Each log event produces no more than one call to w.Write. +// The passed Writer must be safe for concurrent use by multiple goroutines if +// the returned Logger will be used concurrently. +func NewIdasLogger(w io.Writer) log.Logger { + return &idasLogger{w} +} diff --git a/pkg/service/models/app.go b/pkg/service/models/app.go new file mode 100644 index 0000000..a10cf73 --- /dev/null +++ b/pkg/service/models/app.go @@ -0,0 +1,37 @@ +package models + +type GrantType string + +const ( + GrantTypeAuthorizationCode GrantType = "authorization_code" +) + +type GrantMode int8 + +const ( + GrantModeManual GrantMode = 0 // 全员均可登陆 + GrantModeFull GrantMode = 1 // 手动授权 +) + +type App struct { + Model + Name string `gorm:"type:varchar(50);"` + Description string `gorm:"type:varchar(50);"` + Avatar string `gorm:"type:varchar(200)"` + GrantType GrantType `gorm:"type:varchar(20);" json:"grantType"` + GrantMode GrantMode `gorm:"type:varchar(20);" json:"grantMode"` +} + +type AppRole struct { + Model + Name string `gorm:"type:varchar(50);"` + Description string `gorm:"type:varchar(50);"` +} + +type AppUser struct { + Model + AppId string `json:"appId" gorm:"type:char(32)" json:"appId"` + App *App `json:"app,omitempty"` + UserId string `json:"userId" gorm:"type:char(32)" json:"userId"` + User *User `json:"user,omitempty"` +} diff --git a/pkg/service/models/base.go b/pkg/service/models/base.go new file mode 100644 index 0000000..0545a52 --- /dev/null +++ b/pkg/service/models/base.go @@ -0,0 +1,43 @@ +package models + +import ( + "strings" + "time" + + "github.com/pkg/errors" + uuid "github.com/satori/go.uuid" + "gorm.io/gorm" +) + +func NewId() string { + return strings.ReplaceAll(uuid.NewV4().String(), "-", "") +} + +type Model struct { + Id string `json:"id" gorm:"primary_key;type:char(32)" valid:"required"` + CreateTime time.Time `json:"createTime,omitempty" gorm:"type:datetime;omitempty"` + UpdateTime time.Time `json:"updateTime,omitempty" gorm:"type:datetime;omitempty"` + IsDelete bool `json:"isDelete" gorm:"not null;default:0"` +} + +func (model *Model) BeforeCreate(db *gorm.DB) error { + if model.Id == "" { + id := NewId() + if len(id) != 32 { + return errors.New("生成ID失败: " + id) + } + db.Statement.SetColumn("Id", id) + } + if !model.UpdateTime.IsZero() { + db.Statement.SetColumn("CreateTime", time.Now().UTC()) + } + if !model.CreateTime.IsZero() { + db.Statement.SetColumn("UpdateTime", time.Now().UTC()) + } + return nil +} + +func (model *Model) BeforeSave(db *gorm.DB) error { + db.Statement.SetColumn("UpdateTime", time.Now().UTC()) + return nil +} diff --git a/pkg/service/models/session.go b/pkg/service/models/session.go new file mode 100644 index 0000000..09cffc6 --- /dev/null +++ b/pkg/service/models/session.go @@ -0,0 +1,12 @@ +package models + +import ( + "database/sql" + "time" +) + +type Session struct { + Key string `gorm:"primary_key;type:char(32)"` + Data sql.RawBytes + Expiry time.Time `gorm:"type:datetime;omitempty" json:"update_time,omitempty"` +} diff --git a/pkg/service/models/user.go b/pkg/service/models/user.go new file mode 100644 index 0000000..5508872 --- /dev/null +++ b/pkg/service/models/user.go @@ -0,0 +1,55 @@ +package models + +import ( + "crypto/sha1" + "database/sql" + "encoding/json" + "time" +) + +type UserStatus int8 + +const ( + UserStatusUnknown UserStatus = iota + UserStatusNormal + UserStatusDisable +) + +type UserRole string + +const ( + UserRoleUser UserRole = "user" + UserRoleAdmin UserRole = "admin" +) + +type User struct { + Model + Username string `gorm:"type:varchar(20);" json:"username"` + Salt sql.RawBytes `gorm:"type:tinyblob;" json:"-" ` + Password sql.RawBytes `gorm:"type:tinyblob;" json:"password,omitempty"` + Email string `gorm:"type:varchar(50);" json:"email"` + PhoneNumber string `json:"phoneNumber"` + FullName string `gorm:"type:varchar(20);" json:"fullName"` + Avatar string `json:"avatar"` + Status UserStatus `gorm:"not null;default:0" json:"status"` + LoginTime time.Time `json:"loginTime"` + Role UserRole `json:"role"` + Storage string `gorm:"-" json:"storage"` +} + +func (u User) MarshalJSON() ([]byte, error) { + type plain User + u.Password = nil + return json.Marshal(plain(u)) +} + +func (u User) GenSecret(password ...string) []byte { + sha := sha1.New() + sha.Write([]byte(u.Salt)) + if len(password) > 0 { + sha.Write([]byte(password[0])) + } else { + sha.Write(u.Password) + } + return sha.Sum(nil) +} diff --git a/pkg/service/mysqlservice/app.go b/pkg/service/mysqlservice/app.go new file mode 100644 index 0000000..7efd9c1 --- /dev/null +++ b/pkg/service/mysqlservice/app.go @@ -0,0 +1,20 @@ +package mysqlservice + +import ( + "context" + + "idas/pkg/client/mysql" + "idas/pkg/service/models" +) + +type AppService struct { + *mysql.Client +} + +func (a AppService) SetupJoinTable() error { + return a.Session(context.Background()).SetupJoinTable(&models.App{}, "User", models.AppUser{}) +} + +func NewAppService(client *mysql.Client) *AppService { + return &AppService{Client: client} +} diff --git a/pkg/service/mysqlservice/service.go b/pkg/service/mysqlservice/service.go new file mode 100644 index 0000000..3d29b03 --- /dev/null +++ b/pkg/service/mysqlservice/service.go @@ -0,0 +1,5 @@ +package mysqlservice + +//var ( +// DatabaseConnectError = errors.New("database connect error") +//) diff --git a/pkg/service/mysqlservice/session.go b/pkg/service/mysqlservice/session.go new file mode 100644 index 0000000..77ce3d7 --- /dev/null +++ b/pkg/service/mysqlservice/session.go @@ -0,0 +1,86 @@ +package mysqlservice + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + uuid "github.com/satori/go.uuid" + + "idas/pkg/client/mysql" + "idas/pkg/global" + "idas/pkg/service/models" +) + +type SessionService struct { + *mysql.Client +} + +func (s SessionService) OAuthAuthorize(ctx context.Context, responseType, clientId, redirectURI string) (redirect string, err error) { + panic("implement me") +} + +func (s SessionService) GetOAuthTokenByAuthorizationCode(ctx context.Context, code, clientId, redirectURI string) (accessToken, refreshToken string, expiresIn int, err error) { + panic("implement me") +} + +func (s SessionService) GetOAuthTokenByPassword(ctx context.Context, username string, password string) (accessToken, refreshToken string, expiresIn int, err error) { + panic("implement me") +} + +func (s SessionService) RefreshOAuthTokenByAuthorizationCode(ctx context.Context, token, clientId, clientSecret string) (accessToken, refreshToken string, expiresIn int, err error) { + panic("implement me") +} + +func (s SessionService) RefreshOAuthTokenByPassword(ctx context.Context, token, username, password string) (accessToken, refreshToken string, expiresIn int, err error) { + panic("implement me") +} + +func (s SessionService) AutoMigrate(ctx context.Context) error { + return s.Session(ctx).AutoMigrate(&models.Session{}) +} + +func (s SessionService) SetLoginSession(ctx context.Context, user *models.User) (cookie string, err error) { + sessionId := strings.ReplaceAll(uuid.NewV4().String(), "-", "") + var session models.Session + session.Data, err = user.MarshalJSON() + if err != nil { + return "", err + } + session.Expiry = time.Now().UTC().Add(global.LoginSessionExpiration) + session.Key = sessionId + if err = s.Session(ctx).Create(&session).Error; err != nil { + return "", err + } + return fmt.Sprintf("%s=%s; Path=/;Expires=%s", global.LoginSession, sessionId, session.Expiry.Format(global.LoginSessionExpiresFormat)), nil +} + +func NewSessionService(client *mysql.Client) *SessionService { + return &SessionService{Client: client} +} + +func (s SessionService) GetLoginSession(ctx context.Context, id string) (*models.User, string, error) { + session := models.Session{Key: id} + + if err := s.Session(ctx).First(&session).Error; err != nil { + return nil, "用户未登录或身份已过期", err + } + if session.Expiry.Before(time.Now().UTC()) { + return nil, "", fmt.Errorf("用户未登录或身份已过期") + } + var user models.User + if err := json.Unmarshal(session.Data, &user); err != nil { + return nil, "用户未登录或身份已过期", fmt.Errorf("会话数据异常: %s", err) + } + return &user, "", nil +} + +func (s SessionService) DeleteLoginSession(ctx context.Context, id string) (string, error) { + session := models.Session{Key: id} + if err := s.Session(ctx).Delete(&session).Error; err != nil { + return "", err + } + return fmt.Sprintf("%s=%s; Path=/;Expires=%s", global.LoginSession, id, time.Now().UTC().Format(global.LoginSessionExpiresFormat)), nil +} diff --git a/pkg/service/mysqlservice/user.go b/pkg/service/mysqlservice/user.go new file mode 100644 index 0000000..bf6112f --- /dev/null +++ b/pkg/service/mysqlservice/user.go @@ -0,0 +1,172 @@ +package mysqlservice + +import ( + "bytes" + "context" + "fmt" + "reflect" + + "idas/pkg/client/mysql" + "idas/pkg/service/models" +) + +type User struct{} + +type UserService struct { + *mysql.Client + Name string +} + +func (s UserService) AutoMigrate(ctx context.Context) error { + return s.Session(ctx).AutoMigrate(&models.User{}) +} + +func (s UserService) VerifyPassword(ctx context.Context, username string, password string) (*models.User, error) { + var user models.User + if err := s.Session(ctx).Where("username = ? or email = ?", username, username).First(&user).Error; err != nil { + return nil, err + } + + if bytes.Equal(user.GenSecret(password), user.Password) { + return nil, fmt.Errorf("用户名或密码错误") + } + return &user, nil +} + +func NewUserService(name string, client *mysql.Client) *UserService { + return &UserService{Name: name, Client: client} +} + +func (s UserService) GetUsers(ctx context.Context, keyword string, status models.UserStatus, current int64, pageSize int64) (users []*models.User, total int64, err error) { + query := s.Session(ctx).Where("username like ?", fmt.Sprintf("%%%s%%", keyword)) + if status != models.UserStatusUnknown { + query = query.Where("status", status) + } + if err = query.Order("username,id").Limit(int(pageSize)).Offset(int((current - 1) * pageSize)).Find(&users).Error; err != nil { + return nil, 0, err + } else if err = query.Count(&total).Error; err != nil { + return nil, 0, err + } else { + for _, user := range users { + user.Storage = s.Name + } + return users, total, nil + } +} + +func (s UserService) PatchUsers(ctx context.Context, patch []map[string]interface{}) (int64, string, error) { + var patchCount int64 + tx := s.Session(ctx).Begin() + defer tx.Rollback() + updateQuery := tx.Model(&models.User{}).Select("is_delete", "status") + var newPatch map[string]interface{} + var newPatchIds []string + for _, patchInfo := range patch { + tmpPatch := map[string]interface{}{} + var tmpPatchId string + for name, value := range patchInfo { + if name != "id" { + tmpPatch[name] = value + } else { + tmpPatchId, _ = value.(string) + } + } + if tmpPatchId == "" { + return 0, "", fmt.Errorf("parameter exception: invalid id") + } else if len(tmpPatch) == 0 { + return 0, "", fmt.Errorf("parameter exception: update content is empty") + } + if len(newPatchIds) == 0 { + newPatchIds = append(newPatchIds, tmpPatchId) + newPatch = tmpPatch + } else if reflect.DeepEqual(tmpPatch, newPatch) { + newPatchIds = append(newPatchIds, tmpPatchId) + } else { + patched := updateQuery.Where("id in ?", newPatchIds).Updates(newPatch) + if err := patched.Error; err != nil { + return 0, "", err + } + patchCount = patched.RowsAffected + newPatchIds = []string{} + newPatch = map[string]interface{}{} + } + } + if len(newPatchIds) > 0 { + patched := updateQuery.Where("id in ?", newPatchIds).Updates(newPatch) + if err := patched.Error; err != nil { + return 0, "", err + } + patchCount = patched.RowsAffected + } + if err := tx.Commit().Error; err != nil { + return 0, "", err + } + return patchCount, "", nil +} + +func (s UserService) DeleteUsers(ctx context.Context, id []string) (int64, string, error) { + deleted := s.Session(ctx).Model(&models.User{}).Where("id in ?", id).Update("is_delete", true) + if err := deleted.Error; err != nil { + return deleted.RowsAffected, "", err + } + return deleted.RowsAffected, "", nil +} + +func (s UserService) UpdateUser(ctx context.Context, user *models.User, updateColumns ...string) (*models.User, string, error) { + tx := s.Session(ctx).Begin() + defer tx.Rollback() + q := tx.Omit("create_time") + if len(updateColumns) != 0 { + q = q.Select(updateColumns) + } else { + q = q.Omit("login_time", "password", "salt") + } + + if err := q.Updates(&user).Error; err != nil { + return nil, "", err + } + if err := tx.Find(&user).Error; err != nil { + return nil, "", err + } + if err := tx.Commit().Error; err != nil { + return nil, "", err + } + return user, "", nil +} + +func (s UserService) GetUserInfo(ctx context.Context, id string, username string) (*models.User, string, error) { + conn := s.Session(ctx) + var user models.User + if err := conn.Where("id = ? or username = ?", id, username).First(&user).Error; err != nil { + return nil, "", err + } + return &user, "", nil +} + +func (s UserService) CreateUser(ctx context.Context, user *models.User) (*models.User, string, error) { + conn := s.Session(ctx) + if err := conn.Create(&user).Error; err != nil { + return nil, "", err + } + return user, "", nil +} + +func (s UserService) PatchUser(ctx context.Context, patch map[string]interface{}) (*models.User, string, error) { + if id, ok := patch["id"].(string); ok { + tx := s.Session(ctx).Begin() + user := models.User{Model: models.Model{Id: id}} + if err := tx.Model(&models.User{}).Where("id = ?", id).Updates(patch).Error; err != nil { + return nil, "", err + } else if err = tx.First(&user).Error; err != nil { + return nil, "", err + } + tx.Commit() + return &user, "", nil + } + return nil, "用户ID未指定", fmt.Errorf("用户ID未指定") +} + +func (s UserService) DeleteUser(ctx context.Context, id string) (string, error) { + _, msg, err := s.DeleteUsers(ctx, []string{id}) + return msg, err +} diff --git a/pkg/service/redisservice/session.go b/pkg/service/redisservice/session.go new file mode 100644 index 0000000..f268395 --- /dev/null +++ b/pkg/service/redisservice/session.go @@ -0,0 +1,79 @@ +package redisservice + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + uuid "github.com/satori/go.uuid" + + "idas/pkg/client/redis" + "idas/pkg/global" + "idas/pkg/service/models" +) + +type SessionService struct { + *redis.Client +} + +func (s SessionService) OAuthAuthorize(ctx context.Context, responseType, clientId, redirectURI string) (redirect string, err error) { + panic("implement me") +} + +func (s SessionService) GetOAuthTokenByAuthorizationCode(ctx context.Context, code, clientId, redirectURI string) (accessToken, refreshToken string, expiresIn int, err error) { + panic("implement me") +} + +func (s SessionService) GetOAuthTokenByPassword(ctx context.Context, username string, password string) (accessToken, refreshToken string, expiresIn int, err error) { + panic("implement me") +} + +func (s SessionService) RefreshOAuthTokenByAuthorizationCode(ctx context.Context, token, clientId, clientSecret string) (accessToken, refreshToken string, expiresIn int, err error) { + panic("implement me") +} + +func (s SessionService) RefreshOAuthTokenByPassword(ctx context.Context, token, username, password string) (accessToken, refreshToken string, expiresIn int, err error) { + panic("implement me") +} + +func (s SessionService) AutoMigrate(ctx context.Context) error { + return nil +} + +func (s SessionService) GetLoginSession(ctx context.Context, sessionId string) (user *models.User, msg string, err error) { + user = new(models.User) + redisClt := s.Redis(ctx) + sessionValue, err := redisClt.Get(fmt.Sprintf("%s:%s", global.LoginSession, sessionId)).Bytes() + if err != nil { + return nil, "获取用户会话信息失败", err + } else if err = json.Unmarshal(sessionValue, user); err != nil { + return nil, "获取用户会话信息失败", err + } + return user, "", nil +} + +func (s SessionService) SetLoginSession(ctx context.Context, user *models.User) (string, error) { + sessionId := strings.ReplaceAll(uuid.NewV4().String(), "-", "") + redisClt := s.Redis(ctx) + user.Password = nil + user.Salt = nil + if userb, err := json.Marshal(user); err != nil { + return "", err + } else if err := redisClt.Set(fmt.Sprintf("%s:%s", global.LoginSession, sessionId), userb, global.LoginSessionExpiration).Err(); err != nil { + return "", err + } else { + return fmt.Sprintf("%s=%s; Path=/;Expires=%s", global.LoginSession, sessionId, time.Now().UTC().Add(global.LoginSessionExpiration).Format(global.LoginSessionExpiresFormat)), nil + } +} + +func (s SessionService) DeleteLoginSession(ctx context.Context, sessionId string) (string, error) { + redisClt := s.Redis(ctx) + _ = redisClt.Del(fmt.Sprintf("%s:%s", global.LoginSession, sessionId)).Val() + return "", nil +} + +func NewSessionService(client *redis.Client) *SessionService { + return &SessionService{Client: client} +} diff --git a/pkg/service/service.go b/pkg/service/service.go new file mode 100644 index 0000000..9d585ed --- /dev/null +++ b/pkg/service/service.go @@ -0,0 +1,231 @@ +package service + +import ( + "context" + "fmt" + "time" + + "idas/config" + "idas/pkg/client/mysql" + "idas/pkg/client/redis" + "idas/pkg/errors" + "idas/pkg/service/models" + "idas/pkg/service/mysqlservice" + "idas/pkg/service/redisservice" +) + +type migrator interface { + AutoMigrate(ctx context.Context) error +} + +type Service interface { + migrator + SessionService + GetUsers(ctx context.Context, storage string, keyword string, status models.UserStatus, current int64, pageSize int64) (users []*models.User, total int64, err error) + PatchUsers(ctx context.Context, storage string, patch []map[string]interface{}) (count int64, msg string, err error) + DeleteUsers(ctx context.Context, storage string, id []string) (count int64, msg string, err error) + UpdateUser(ctx context.Context, storage string, user *models.User, updateColumns ...string) (*models.User, string, error) + GetUserInfo(ctx context.Context, storage string, id string, username string) (*models.User, string, error) + CreateUser(ctx context.Context, storage string, user *models.User) (*models.User, string, error) + PatchUser(ctx context.Context, storage string, user map[string]interface{}) (*models.User, string, error) + DeleteUser(ctx context.Context, storage string, id string) (string, error) + CreateLoginSession(ctx context.Context, username string, password string) (string, error) + GetUserSource(ctx context.Context) (data map[string]string, total int64, err error) +} + +func (s Set) GetUserService(name string) UserService { + for n, c := range s.userService { + if n == name /*|| len(name) == 0 */ { + return c + } + } + return nil +} + +func (s Set) UserServiceDo(name string, f func(service UserService)) error { + service := s.GetUserService(name) + if service == nil { + return errors.StatusNotFound("User") + } + f(service) + return nil +} + +func (s Set) SafeGetUserService(name string) UserService { + for n, c := range s.userService { + if n == name { + return c + } + } + for _, service := range s.userService { + return service + } + return nil +} + +type Set struct { + userService map[string]UserService + SessionService +} + +func (s Set) GetUserSource(ctx context.Context) (data map[string]string, total int64, err error) { + data = map[string]string{} + for name := range s.userService { + data[name] = name + } + return +} + +func (s Set) GetUsers(ctx context.Context, storage string, keyword string, status models.UserStatus, current int64, pageSize int64) (users []*models.User, total int64, err error) { + return s.SafeGetUserService(storage).GetUsers(ctx, keyword, status, current, pageSize) +} + +func (s Set) PatchUsers(ctx context.Context, storage string, patch []map[string]interface{}) (total int64, msg string, err error) { + err = s.UserServiceDo(storage, func(service UserService) { + total, msg, err = service.PatchUsers(ctx, patch) + }) + return +} + +func (s Set) DeleteUsers(ctx context.Context, storage string, id []string) (total int64, msg string, err error) { + err = s.UserServiceDo(storage, func(service UserService) { + total, msg, err = service.DeleteUsers(ctx, id) + }) + return +} + +func (s Set) UpdateUser(ctx context.Context, storage string, user *models.User, updateColumns ...string) (u *models.User, msg string, err error) { + err = s.UserServiceDo(storage, func(service UserService) { + u, msg, err = service.UpdateUser(ctx, user, updateColumns...) + }) + return +} + +func (s Set) GetUserInfo(ctx context.Context, storage string, id string, username string) (user *models.User, msg string, err error) { + err = s.UserServiceDo(storage, func(service UserService) { + user, msg, err = service.GetUserInfo(ctx, id, username) + }) + return +} + +func (s Set) CreateUser(ctx context.Context, storage string, user *models.User) (u *models.User, msg string, err error) { + err = s.UserServiceDo(storage, func(service UserService) { + u, msg, err = service.CreateUser(ctx, user) + }) + return +} + +func (s Set) PatchUser(ctx context.Context, storage string, user map[string]interface{}) (u *models.User, msg string, err error) { + err = s.UserServiceDo(storage, func(service UserService) { + u, msg, err = service.PatchUser(ctx, user) + }) + return +} + +func (s Set) DeleteUser(ctx context.Context, storage string, id string) (msg string, err error) { + err = s.UserServiceDo(storage, func(service UserService) { + msg, err = service.DeleteUser(ctx, id) + }) + return +} + +func (s Set) VerifyPassword(ctx context.Context, username string, password string) (user *models.User, err error) { + for storageName, userService := range s.userService { + user, err = userService.VerifyPassword(ctx, username, password) + if err == nil { + user.Storage = storageName + return user, nil + } + } + return nil, errors.UnauthorizedError +} + +func (s Set) AutoMigrate(ctx context.Context) error { + for _, svc := range s.userService { + if err := svc.AutoMigrate(ctx); err != nil { + return err + } + } + return s.SessionService.AutoMigrate(ctx) +} + +func (s Set) CreateLoginSession(ctx context.Context, username string, password string) (session string, err error) { + user, err := s.VerifyPassword(ctx, username, password) + if user == nil { + return "", err + } + user.LoginTime = time.Now().UTC() + if user, _, err = s.GetUserService(user.Storage).UpdateUser(ctx, user, "login_time"); err != nil { + return "", err + } + return s.SessionService.SetLoginSession(ctx, user) +} + +type SessionService interface { + migrator + SetLoginSession(ctx context.Context, user *models.User) (string, error) + DeleteLoginSession(ctx context.Context, session string) (string, error) + GetLoginSession(ctx context.Context, id string) (*models.User, string, error) + OAuthAuthorize(ctx context.Context, responseType, clientId, redirectURI string) (redirect string, err error) + GetOAuthTokenByAuthorizationCode(ctx context.Context, code, clientId, redirectURI string) (accessToken, refreshToken string, expiresIn int, err error) + RefreshOAuthTokenByAuthorizationCode(ctx context.Context, token, clientId, clientSecret string) (accessToken, refreshToken string, expiresIn int, err error) + GetOAuthTokenByPassword(ctx context.Context, username string, password string) (accessToken, refreshToken string, expiresIn int, err error) + RefreshOAuthTokenByPassword(ctx context.Context, token, username, password string) (accessToken, refreshToken string, expiresIn int, err error) +} + +type UserService interface { + migrator + GetUsers(ctx context.Context, keyword string, status models.UserStatus, current int64, pageSize int64) (users []*models.User, total int64, err error) + PatchUsers(ctx context.Context, patch []map[string]interface{}) (count int64, msg string, err error) + DeleteUsers(ctx context.Context, id []string) (count int64, msg string, err error) + UpdateUser(ctx context.Context, user *models.User, updateColumns ...string) (*models.User, string, error) + GetUserInfo(ctx context.Context, id string, username string) (*models.User, string, error) + CreateUser(ctx context.Context, user *models.User) (*models.User, string, error) + PatchUser(ctx context.Context, user map[string]interface{}) (*models.User, string, error) + DeleteUser(ctx context.Context, id string) (string, error) + VerifyPassword(ctx context.Context, username string, password string) (*models.User, error) +} + +// New returns a basic Service with all of the expected middlewares wired in. +func New(ctx context.Context) Service { + userService := map[string]UserService{} + var sessionService SessionService + if len(config.Get().GetStorage().GetUser()) > 0 { + for _, userStorage := range config.Get().GetStorage().GetUser() { + if _, ok := userService[userStorage.GetName()]; ok { + panic(any(fmt.Errorf("Failed to init UserService: duplicate datasource: %T ", userStorage.Name))) + } + switch userSource := userStorage.GetSource().(type) { + case *config.Storage_Mysql: + if client, err := mysql.NewMySQLClient(ctx, userSource.Mysql); err != nil { + panic(any(fmt.Errorf("初始化UserService失败: MySQL数据库连接失败: %s", err))) + } else { + userService[userStorage.GetName()] = mysqlservice.NewUserService(userStorage.GetName(), client) + } + default: + panic(any(fmt.Errorf("Failed to init UserService: Unknown datasource: %T ", userSource))) + } + } + } + + switch sessionSource := config.Get().GetStorage().GetSession().GetSource().(type) { + case *config.Storage_Mysql: + if client, err := mysql.NewMySQLClient(ctx, sessionSource.Mysql); err != nil { + panic(any(fmt.Errorf("初始化UserService失败: MySQL数据库连接失败: %s", err))) + } else { + sessionService = mysqlservice.NewSessionService(client) + } + case *config.Storage_Redis: + if client, err := redis.NewRedisClient(ctx, sessionSource.Redis); err != nil { + panic(any(fmt.Errorf("初始化UserService失败: MySQL数据库连接失败: %s", err))) + } else { + sessionService = redisservice.NewSessionService(client) + } + default: + panic(any(fmt.Errorf("初始化UserService失败: 未知的数据源类型: %T", sessionSource))) + } + return &Set{ + userService: userService, + SessionService: sessionService, + } +} diff --git a/pkg/transport/http.go b/pkg/transport/http.go new file mode 100644 index 0000000..9209dc6 --- /dev/null +++ b/pkg/transport/http.go @@ -0,0 +1,164 @@ +package transport + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "github.com/asaskevich/govalidator" + "github.com/emicklei/go-restful/v3" + kitendpoint "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/tracing/opentracing" + "github.com/go-kit/kit/tracing/zipkin" + "github.com/go-kit/kit/transport" + httptransport "github.com/go-kit/kit/transport/http" + "github.com/go-kit/log" + "github.com/go-kit/log/level" + stdopentracing "github.com/opentracing/opentracing-go" + stdzipkin "github.com/openzipkin/zipkin-go" + + "idas/pkg/endpoint" + "idas/pkg/errors" + "idas/pkg/global" + "idas/pkg/logs" + "idas/pkg/utils/buffer" + "idas/pkg/utils/httputil" +) + +// NewHTTPHandler returns an HTTP handler that makes a set of endpoints +// available on predefined paths. +func NewHTTPHandler(endpoints endpoint.Set, otTracer stdopentracing.Tracer, zipkinTracer *stdzipkin.Tracer, logger log.Logger) http.Handler { + options := []httptransport.ServerOption{ + httptransport.ServerErrorEncoder(errorEncoder), + httptransport.ServerErrorHandler(transport.NewLogErrorHandler(logger)), + } + + if zipkinTracer != nil { + // Zipkin HTTP Server Trace can either be instantiated per endpoint with a + // provided operation name or a global tracing service can be instantiated + // without an operation name and fed to each Go kit endpoint as ServerOption. + // In the latter case, the operation name will be the endpoint's http method. + // We demonstrate a global tracing service here. + options = append(options, zipkin.HTTPServerTrace(zipkinTracer)) + } + + m := restful.NewContainer() + options = append(options, httptransport.ServerBefore(opentracing.HTTPToContext(otTracer, "Concat", logger))) + InstallHTTPApi(logger, m, options, endpoints) + return m +} + +func errorEncoder(ctx context.Context, err error, w http.ResponseWriter) { + traceId := ctx.Value(global.TraceIdName).(string) + resp := responseWrapper{ + ErrorMessage: err.Error(), + TraceId: traceId, + Success: false, + } + if serverErr, ok := err.(errors.ServerError); ok { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(serverErr.StatusCode()) + resp.ErrorCode = serverErr.Code() + } else { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusInternalServerError) + } + if err = json.NewEncoder(w).Encode(resp); err != nil { + level.Info(ctx.Value(global.LoggerName).(log.Logger)).Log("msg", "failed to write response") + } +} + +type responseWrapper struct { + Success bool `json:"success"` + Data interface{} `json:"data,inline"` + ErrorCode string `json:"errorCode,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` + TraceId string `json:"traceId"` + Current int64 `json:"current,omitempty"` + PageSize int64 `json:"pageSize,omitempty"` + Total int64 `json:"total"` +} + +// decodeHTTPRequest Decode HTTP requests into request types +func decodeHTTPRequest[RequestType any](_ context.Context, r *http.Request) (interface{}, error) { + var req RequestType + var err error + restfulReq := r.Context().Value(global.RestfulRequestContextName).(*restful.Request) + restfulResp := r.Context().Value(global.RestfulResponseContextName).(*restful.Response) + logger := logs.GetContextLogger(r.Context()) + query := restfulReq.Request.URL.Query() + if len(query) > 0 { + if err = httputil.UnmarshalURLValues(query, &req); err != nil { + return nil, fmt.Errorf("failed to decode url query:%s", err) + } + } + contentType := r.Header.Get("Content-Type") + if contentType == "application/x-www-form-urlencoded" { + if err = r.ParseForm(); err != nil { + return nil, fmt.Errorf("failed to parse form data:%s", err) + } else if len(r.Form) > 0 { + if err = httputil.UnmarshalURLValues(r.Form, &req); err != nil { + return nil, fmt.Errorf("failed to decode form data:%s", err) + } + } + } else if strings.HasPrefix(contentType, "multipart/form-data") { + if err = r.ParseMultipartForm(1e6); err != nil { + return nil, fmt.Errorf("failed to parse multipart form data:%s", err) + } else if len(r.Form) > 0 { + if err = httputil.UnmarshalURLValues(r.Form, &req); err != nil { + return nil, fmt.Errorf("failed to decode multipart form data:%s", err) + } + } + } else if len(contentType) > 0 { + logWriter := logs.NewWriterAdapter(level.Debug(log.With(logger, "caller", log.Caller(6))), logs.Prefix("decode http request: ", true)) + if err = json.NewDecoder(io.TeeReader(r.Body, buffer.LimitWriter(logWriter, 1024, buffer.LimitWriterIgnoreError))).Decode(&req); err != nil { + return nil, fmt.Errorf("failed to decode request body:%s", err) + } + } + if len(restfulReq.PathParameters()) > 0 { + if err = httputil.UnmarshalURLValues(httputil.MapToURLValues(restfulReq.PathParameters()), &req); err != nil { + return nil, fmt.Errorf("failed to decode path parameters:%s", err) + } + } + + if rr, ok := interface{}(&req).(endpoint.RestfulRequester); ok { + rr.SetRestfulRequest(restfulReq) + rr.SetRestfulResponse(restfulResp) + } + level.Debug(logger).Log("msg", "decoded http request", "req", fmt.Sprintf("%#v", req)) + if ok, err := govalidator.ValidateStruct(req); err != nil { + return &req, errors.NewServerError(http.StatusBadRequest, err.Error()) + } else if !ok { + return &req, errors.NewServerError(http.StatusBadRequest, "params error") + } + return &req, err +} + +// encodeHTTPResponse Encode the response as an HTTP response message +func encodeHTTPResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { + logger := logs.GetContextLogger(ctx) + if f, ok := response.(kitendpoint.Failer); ok && f.Failed() != nil { + errorEncoder(ctx, f.Failed(), w) + return nil + } + w.Header().Set("Content-Type", "application/json; charset=utf-8") + traceId := ctx.Value(global.TraceIdName).(string) + resp := responseWrapper{Success: true, TraceId: traceId} + if l, ok := response.(endpoint.Lister); ok { + resp.Data = l.GetData() + resp.Total = l.GetTotal() + resp.PageSize = l.GetPageSize() + resp.Current = l.GetCurrent() + } else { + if t, ok := response.(endpoint.Total); ok { + resp.Total = t.GetTotal() + } + resp.Data = response + } + + logWriter := logs.NewWriterAdapter(level.Debug(log.With(logger, "resp", fmt.Sprintf("%#v", resp), "caller", log.Caller(6))), logs.Prefix("encoded http response: ", true)) + return json.NewEncoder(io.MultiWriter(w, buffer.LimitWriter(logWriter, 1024, buffer.LimitWriterIgnoreError))).Encode(resp) +} diff --git a/pkg/transport/http_api.go b/pkg/transport/http_api.go new file mode 100644 index 0000000..b3aa1f6 --- /dev/null +++ b/pkg/transport/http_api.go @@ -0,0 +1,91 @@ +package transport + +import ( + "context" + stdlog "log" + + "github.com/emicklei/go-restful/v3" + kitendpoint "github.com/go-kit/kit/endpoint" + httptransport "github.com/go-kit/kit/transport/http" + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "k8s.io/apimachinery/pkg/runtime/schema" + + "idas/pkg/endpoint" + "idas/pkg/global" +) + +func WrapHTTPHandler(h *httptransport.Server) func(*restful.Request, *restful.Response) { + return func(req *restful.Request, resp *restful.Response) { + ctx := req.Request.Context() + if ctx == nil { + ctx = context.Background() + } + request := req.Request.WithContext(context.WithValue(context.WithValue(ctx, global.RestfulResponseContextName, resp), global.RestfulRequestContextName, req)) + h.ServeHTTP(resp, request) + } +} + +func NewKitHTTPServer[RequestType any](dp kitendpoint.Endpoint, options []httptransport.ServerOption) restful.RouteFunction { + return WrapHTTPHandler(httptransport.NewServer( + dp, + decodeHTTPRequest[RequestType], + encodeHTTPResponse, + options..., + )) +} + +func NewWebService(rootPath string, gv schema.GroupVersion, doc string) *restful.WebService { + webservice := restful.WebService{} + webservice.Path(rootPath + "/" + gv.String()). + Consumes(restful.MIME_JSON). + Produces(restful.MIME_JSON).Doc(doc) + return &webservice +} + +func NewSimpleWebService(rootPath string, doc string) *restful.WebService { + webservice := restful.WebService{} + webservice.Path(rootPath). + Consumes(restful.MIME_JSON). + Produces(restful.MIME_JSON).Doc(doc) + return &webservice +} + +func InstallHTTPApi(logger log.Logger, container *restful.Container, options []httptransport.ServerOption, endpoints endpoint.Set) { + container.Filter(HTTPLogging) + restful.TraceLogger(stdlog.New(log.NewStdlibAdapter(level.Info(logger)), "[restful]", stdlog.LstdFlags|stdlog.Lshortfile)) + container.Filter(HTTPLoginAuthentication(endpoints)) + v1Ws := NewSimpleWebService("/api/v1", "基础接口") + v1Ws.Route(v1Ws.POST("/login").Doc("用户登陆").To(NewKitHTTPServer[endpoint.UserLoginRequest](endpoints.UserLogin, options)).Metadata(global.MetaNeedLogin, false)) + v1Ws.Route(v1Ws.POST("/logout").Doc("用户退出登录").To(NewKitHTTPServer[endpoint.UserLogoutRequest](endpoints.UserLogout, options))) + v1Ws.Route(v1Ws.GET("/user").Doc("获取当前登陆用户信息").To(NewKitHTTPServer[endpoint.CurrentUserRequest](endpoints.CurrentUser, options))) + container.Add(v1Ws) + managerWs := NewWebService("/api", schema.GroupVersion{Group: "manager", Version: "v1"}, "管理接口") + + // 用户管理接口 + managerWs.Route(managerWs.GET("/users").Doc("获取用户列表").To(NewKitHTTPServer[endpoint.GetUsersRequest](endpoints.GetUsers, options))) + managerWs.Route(managerWs.PATCH("/users").Doc("批量更新用户信息(增量)").To(NewKitHTTPServer[endpoint.PatchUsersRequest](endpoints.PatchUsers, options))) + managerWs.Route(managerWs.DELETE("/users").Doc("批量删除用户").To(NewKitHTTPServer[endpoint.DeleteUsersRequest](endpoints.DeleteUsers, options))) + managerWs.Route(managerWs.GET("/users/source").Doc("获取用户存储源").To(NewKitHTTPServer[endpoint.GetUserSourceRequest](endpoints.GetUserSource, options))) + managerWs.Route(managerWs.GET("/user/{id}").Doc("获取用户信息").To(NewKitHTTPServer[endpoint.GetUserRequest](endpoints.GetUserInfo, options))) + managerWs.Route(managerWs.POST("/user/{id}").Doc("创建/更新用户").To(NewKitHTTPServer[endpoint.CreateUserRequest](endpoints.CreateUser, options))) + managerWs.Route(managerWs.PUT("/user/{id}").Doc("更新用户信息(全量)").To(NewKitHTTPServer[endpoint.UpdateUserRequest](endpoints.UpdateUser, options))) + managerWs.Route(managerWs.PATCH("/user/{id}").Doc("更新用户信息(增量)").To(NewKitHTTPServer[endpoint.PatchUserRequest](endpoints.PatchUser, options))) + managerWs.Route(managerWs.DELETE("/user/{id}").Doc("删除用户").To(NewKitHTTPServer[endpoint.DeleteUserRequest](endpoints.DeleteUser, options))) + + managerWs.Route(managerWs.GET("/apps").Doc("获取应用列表").To(NewKitHTTPServer[endpoint.GetUsersRequest](endpoints.GetUsers, options))) + managerWs.Route(managerWs.PATCH("/apps").Doc("批量更新应用信息(增量)").To(NewKitHTTPServer[endpoint.PatchUsersRequest](endpoints.PatchUsers, options))) + managerWs.Route(managerWs.DELETE("/apps").Doc("批量删除应用").To(NewKitHTTPServer[endpoint.DeleteUsersRequest](endpoints.DeleteUsers, options))) + managerWs.Route(managerWs.GET("/app/{id}").Doc("获取用户信息").To(NewKitHTTPServer[endpoint.GetUserRequest](endpoints.GetUserInfo, options))) + managerWs.Route(managerWs.POST("/app/{id}").Doc("创建/更新用户").To(NewKitHTTPServer[endpoint.CreateUserRequest](endpoints.CreateUser, options))) + managerWs.Route(managerWs.PUT("/app/{id}").Doc("更新用户信息(全量)").To(NewKitHTTPServer[endpoint.UpdateUserRequest](endpoints.UpdateUser, options))) + managerWs.Route(managerWs.PATCH("/app/{id}").Doc("更新用户信息(增量)").To(NewKitHTTPServer[endpoint.PatchUserRequest](endpoints.PatchUser, options))) + managerWs.Route(managerWs.DELETE("/app/{id}").Doc("删除用户").To(NewKitHTTPServer[endpoint.DeleteUserRequest](endpoints.DeleteUser, options))) + + container.Add(managerWs) + oauthWs := NewWebService("/api", schema.GroupVersion{Group: "oauth", Version: "v1"}, "OAUTH") + // https://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html + oauthWs.Route(oauthWs.POST("/token").Doc("获取令牌").To(NewKitHTTPServer[endpoint.OAuthTokenRequest](endpoints.UserLogout, options)).Metadata(global.MetaNeedLogin, false)) + oauthWs.Route(oauthWs.POST("/authorize").Doc("应用授权").To(NewKitHTTPServer[endpoint.OAuthAuthorizeRequest](endpoints.OAuthAuthorize, options)).Metadata(global.MetaNeedLogin, false)) + container.Add(oauthWs) +} diff --git a/pkg/transport/http_middleware.go b/pkg/transport/http_middleware.go new file mode 100644 index 0000000..762d9a0 --- /dev/null +++ b/pkg/transport/http_middleware.go @@ -0,0 +1,101 @@ +package transport + +import ( + "bytes" + "context" + "fmt" + "net/http" + "runtime" + "strings" + "time" + + "github.com/emicklei/go-restful/v3" + "github.com/go-kit/log" + "github.com/go-kit/log/level" + uuid "github.com/satori/go.uuid" + + "idas/pkg/endpoint" + "idas/pkg/errors" + "idas/pkg/global" + "idas/pkg/logs" +) + +func HTTPLoginAuthentication(endpoints endpoint.Set) restful.FilterFunction { + return func(req *restful.Request, resp *restful.Response, filterChan *restful.FilterChain) { + if req.SelectedRoute() == nil { + errorEncoder(req.Request.Context(), errors.NewServerError(http.StatusNotFound, "Not Found"+req.Request.RequestURI), resp) + return + } + if needLogin, ok := req.SelectedRoute().Metadata()[global.MetaNeedLogin].(bool); ok && !needLogin { + filterChan.ProcessFilter(req, resp) + return + } + + loginSessionID, err := req.Request.Cookie(global.LoginSession) + if err != nil { + errorEncoder(req.Request.Context(), errors.NewServerError(http.StatusForbidden, "Not logged in or identity expired"), resp) + return + } else if user, err := endpoints.GetLoginSession(req.Request.Context(), loginSessionID.Value); err != nil { + errorEncoder(req.Request.Context(), errors.NewServerError(http.StatusForbidden, "Not logged in or identity expired"), resp) + return + } else if user == nil { + errorEncoder(req.Request.Context(), errors.NewServerError(http.StatusForbidden, "Not logged in or identity expired"), resp) + return + } else { + req.SetAttribute(global.AttrUser, user) + filterChan.ProcessFilter(req, resp) + } + return + } +} + +func HTTPLogging(req *restful.Request, resp *restful.Response, filterChan *restful.FilterChain) { + ctx := req.Request.Context() + if ctx == nil { + ctx = context.Background() + } + traceId := req.HeaderParameter("TraceId") + if len(traceId) > 36 || len(traceId) <= 0 { + if traceId = req.HeaderParameter("X-Request-Id"); len(traceId) > 36 || len(traceId) <= 0 { + traceId = strings.ReplaceAll(uuid.NewV4().String(), "-", "") + } + } + logger := log.With(logs.GetRootLogger(), global.TraceIdName, traceId) + req.Request = req.Request.WithContext(context.WithValue(context.WithValue(ctx, global.TraceIdName, traceId), global.LoggerName, logger)) + start := time.Now() + level.Info(logger).Log( + "msg", "HTTP request received.", + "title", "request", + "[httpURI]", req.Request.RequestURI, + "[method]", req.Request.Method, + "[proto]", req.Request.Proto, + "[contentType]", req.HeaderParameter("Content-Type"), + "[contentLength]", req.Request.ContentLength, + ) + + defer func() { + if r := recover(); r != nil { + errorEncoder(req.Request.Context(), errors.NewServerError(http.StatusForbidden, "Server exception"), resp) + buffer := bytes.NewBufferString(fmt.Sprintf("recover from panic situation: - %v\n", r)) + for i := 2; ; i++ { + _, file, line, ok := runtime.Caller(i) + if !ok { + break + } + buffer.WriteString(fmt.Sprintf(" %s:%d\n", file, line)) + } + level.Error(logger).Log("msg", buffer.String()) + } + logger = log.With(logger, + "msg", "HTTP response send.", + "title", "response", + "[httpURI]", req.Request.RequestURI, + "[status]", resp.StatusCode(), + "[contentType]", resp.Header().Get("Content-Type"), + "[contentLength]", resp.ContentLength(), + ) + + level.Info(logger).Log("[totalTime]", time.Since(start)/time.Millisecond) + }() + filterChan.ProcessFilter(req, resp) +} diff --git a/pkg/utils/buffer/limit.go b/pkg/utils/buffer/limit.go new file mode 100644 index 0000000..0acb7c1 --- /dev/null +++ b/pkg/utils/buffer/limit.go @@ -0,0 +1,48 @@ +package buffer + +import "io" + +type LimitWriterOption func(*LimitedWriter) + +// LimitWriter returns a Writer that reads from r +// but stops with EOF after n bytes. +// The underlying implementation is a *LimitedWriter. +func LimitWriter(w io.Writer, n int64, options ...LimitWriterOption) io.Writer { + writer := &LimitedWriter{W: w, N: n, MaxN: n} + for _, option := range options { + option(writer) + } + return writer +} + +func LimitWriterIgnoreError(lw *LimitedWriter) { + lw.ignoreError = true +} + +// A LimitedWriter reads from R but limits the amount of +// data returned to just N bytes. Each call to Read +// updates N to reflect the new amount remaining. +// Read returns EOF when N <= 0 or when the underlying R returns EOF. +type LimitedWriter struct { + W io.Writer // underlying reader + N int64 // max bytes remaining + MaxN int64 // max bytes remaining + ignoreError bool +} + +func (l *LimitedWriter) Write(p []byte) (n int, err error) { + if l.N <= 0 { + err = io.EOF + } else { + if int64(len(p)) > l.N { + p = p[0:l.N] + } + n, err = l.W.Write(p) + l.N -= int64(n) + } + if l.ignoreError { + return len(p), nil + } + + return +} diff --git a/pkg/utils/errors/errors.go b/pkg/utils/errors/errors.go new file mode 100644 index 0000000..3c0ec54 --- /dev/null +++ b/pkg/utils/errors/errors.go @@ -0,0 +1,69 @@ +package errors + +import ( + "strconv" + "strings" +) + +type MultipleError struct { + errs []error + Sep string + PluralPrefix string + Index bool + AppendBefore func(err error) +} + +func (m *MultipleError) Append(errs ...error) *MultipleError { + for _, err := range errs { + if err != nil { + if m.AppendBefore != nil { + m.AppendBefore(err) + } + m.errs = append(m.errs, errs...) + } + } + return m +} + +func (m *MultipleError) HasError() bool { + return len(m.errs) > 0 +} + +func AppendError(m *MultipleError, errs ...error) *MultipleError { + if m != nil { + _ = m.Append(errs...) + } else if len(errs) > 0 { + m = &MultipleError{errs: errs} + } + return m +} + +func (m MultipleError) Error() string { + ret := make([]string, len(m.errs)) + + for idx, err := range m.errs { + ret[idx] = "" + if m.Index { + ret[idx] = strconv.Itoa(idx) + ". " + } + ret[idx] += err.Error() + } + if len(ret) > 1 { + return m.PluralPrefix + strings.Join(ret, m.Sep) + } + return strings.Join(ret, m.Sep) +} + +func (m *MultipleError) Count() int { + return len(m.errs) +} + +func (m *MultipleError) Clear() { + m.errs = nil +} + +func NewMultipleError(errs ...error) *MultipleError { + return AppendError(nil, errs...) +} + +var _ error = MultipleError{} diff --git a/pkg/utils/httputil/params.go b/pkg/utils/httputil/params.go new file mode 100644 index 0000000..472a1b4 --- /dev/null +++ b/pkg/utils/httputil/params.go @@ -0,0 +1,118 @@ +package httputil + +import ( + "errors" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" +) + +var ErrStruct = errors.New("Unmarshal() expects struct input. ") + +func MapToURLValues(m map[string]string) (vals url.Values) { + for name, val := range m { + vals.Set(name, val) + } + return +} + +// UnmarshalURLValues url.Values to struct +func UnmarshalURLValues(values url.Values, s interface{}) error { + val := reflect.ValueOf(s) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return ErrStruct + } + val = val.Elem() + } + if val.Kind() != reflect.Struct { + return ErrStruct + } + return reflectValueFromTag(values, val) +} + +func reflectValueFromTag(values url.Values, val reflect.Value) error { + typ := val.Type() + for i := 0; i < val.NumField(); i++ { + kt := typ.Field(i) + sv := val.Field(i) + if !(kt.Name[0] >= 'A' && kt.Name[0] <= 'Z') { + continue + } + jsonTag := kt.Tag.Get("json") + + var ( + jsonName = jsonTag + extAttr string + ) + + if idx := strings.Index(jsonTag, ","); idx >= 0 { + jsonName = jsonTag[:idx] + extAttr = jsonTag[idx+1:] + } + if extAttr == "inline" { + if err := reflectValueFromTag(values, sv); err != nil { + return err + } + continue + } else if jsonName == "-" { + continue + } else if jsonName == "" { + jsonName = func(old []byte) string { + if 'A' < old[0] && old[0] < 'Z' { + old[0] += 'a' - 'A' + } + return string(old) + }([]byte(kt.Name)) + } + + fmt.Println(jsonName, values, sv.Kind()) + switch sv.Kind() { + case reflect.Struct: + if err := reflectValueFromTag(values, sv); err != nil { + return err + } + continue + default: + if !values.Has(jsonName) { + continue + } + } + uv := values.Get(jsonName) + switch sv.Kind() { + case reflect.Slice: + + case reflect.String: + sv.SetString(uv) + case reflect.Bool: + b, err := strconv.ParseBool(uv) + if err != nil { + return fmt.Errorf("cast bool has error, expect type: %v ,val: %v ,query key: %v", sv.Type(), uv, jsonName) + } + sv.SetBool(b) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + n, err := strconv.ParseUint(uv, 10, 64) + if err != nil || sv.OverflowUint(n) { + return fmt.Errorf("cast uint has error, expect type: %v ,val: %v ,query key: %v", sv.Type(), uv, jsonName) + } + sv.SetUint(n) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + n, err := strconv.ParseInt(uv, 10, 64) + if err != nil || sv.OverflowInt(n) { + return fmt.Errorf("cast int has error, expect type: %v ,val: %v ,query key: %v", sv.Type(), uv, jsonName) + } + sv.SetInt(n) + case reflect.Float32, reflect.Float64: + n, err := strconv.ParseFloat(uv, sv.Type().Bits()) + if err != nil || sv.OverflowFloat(n) { + return fmt.Errorf("cast float has error, expect type: %v ,val: %v ,query key: %v", sv.Type(), uv, jsonName) + } + sv.SetFloat(n) + default: + return fmt.Errorf("unsupported type: %v ,val: %v ,query key: %v", sv.Type(), uv, jsonName) + } + } + return nil +} diff --git a/pkg/utils/httputil/values.go b/pkg/utils/httputil/values.go new file mode 100644 index 0000000..c25a2bd --- /dev/null +++ b/pkg/utils/httputil/values.go @@ -0,0 +1,229 @@ +package httputil + +import ( + "bytes" + "strconv" + "strings" + "time" +) + +type Value struct { + val string + dftVal string + splitFunc func(v Value) []string +} + +type Option func(value *Value) + +func Default(dftVal string) Option { + return func(value *Value) { + value.dftVal = dftVal + } +} + +func SplitFunc(f func(v Value) []string) Option { + return func(value *Value) { + value.splitFunc = f + } +} + +func NewValue(val string, options ...Option) *Value { + v := &Value{val: val} + for _, option := range options { + option(v) + } + return v +} + +func (v *Value) Set(val string) *Value { + v.val = val + return v +} + +func (v *Value) Default(dftVal string) *Value { + v.dftVal = dftVal + return v +} + +func (v Value) Split(seps ...byte) Values { + val := v.Bytes() + if len(val) == 0 { + return nil + } + if bytes.HasPrefix(val, []byte{'['}) && bytes.HasSuffix(val, []byte{']'}) { + val = bytes.TrimSpace(bytes.TrimSuffix(bytes.TrimPrefix(val, []byte{'['}), []byte{']'})) + } + if len(val) == 0 { + return nil + } + if len(seps) == 0 { + if v.splitFunc != nil { + var vals Values + for _, vv := range v.splitFunc(v) { + vals = append(vals, *NewValue(vv)) + } + return vals + } + seps = []byte{','} + } + var vals []Value +loop: + for pos, i := 0, 0; i < len(val); i++ { + for _, sep := range seps { + if sep == val[i] { + vals = append(vals, Value{val: string(val[pos:i])}) + pos = i + 1 + continue loop + } + } + if i == len(val)-1 { + vals = append(vals, Value{val: string(val[pos:])}) + } + } + return vals +} + +func (v Value) String() string { + if len(v.val) > 0 { + return v.val + } + return v.dftVal +} + +func (v Value) Strings(seps ...byte) []string { + return v.Split(seps...).Strings() +} + +func (v Value) Int() (int, error) { + return strconv.Atoi(strings.TrimSpace(v.String())) +} + +func (v Value) Int32s(seps ...byte) (vals []int, err error) { + return v.Split(seps...).Ints() +} + +func (v Value) Int64() (int64, error) { + return strconv.ParseInt(strings.TrimSpace(v.String()), 10, 64) +} + +func (v Value) Int64s(seps ...byte) (vals []int64, err error) { + return v.Split(seps...).Int64s() +} + +func (v Value) Float32() (float32, error) { + float, err := strconv.ParseFloat(strings.TrimSpace(v.String()), 64) + return float32(float), err +} + +func (v Value) Float32s(seps ...byte) (vals []float32, err error) { + return v.Split(seps...).Float32s() +} + +func (v Value) Float64() (float64, error) { + return strconv.ParseFloat(strings.TrimSpace(v.String()), 64) +} + +func (v Value) Float64s(seps ...byte) (vals []float64, err error) { + return v.Split(seps...).Float64s() +} + +func (v Value) Bool() (bool, error) { + return strconv.ParseBool(strings.TrimSpace(v.String())) +} + +func (v Value) Duration() (time.Duration, error) { + s := strings.TrimSpace(v.String()) + if len(s) > 0 && s[0] == '-' { + duration, err := time.ParseDuration(s[1:]) + return -duration, err + } + return time.ParseDuration(s) +} + +func (v Value) Durations(seps ...byte) (vals []time.Duration, err error) { + return v.Split(seps...).Durations() +} + +func (v Value) Time(layout string) (time.Time, error) { + return time.Parse(layout, v.String()) +} + +func (v Value) Bytes() []byte { + return []byte(v.String()) +} + +type Values []Value + +func (v Values) Durations() (vals []time.Duration, err error) { + for _, s := range v { + f, err := s.Duration() + if err != nil { + return nil, err + } + vals = append(vals, f) + } + return vals, err +} + +func (v Values) Float64s() (vals []float64, err error) { + for _, s := range v { + f, err := s.Float64() + if err != nil { + return nil, err + } + vals = append(vals, f) + + } + return vals, err +} + +func (v Values) Float32s() (vals []float32, err error) { + for _, s := range v { + f, err := s.Float32() + if err != nil { + return nil, err + } + vals = append(vals, f) + } + return vals, err +} + +func (v Values) Int64s() (vals []int64, err error) { + for _, s := range v { + f, err := s.Int64() + if err != nil { + return nil, err + } + vals = append(vals, f) + } + return vals, err +} + +func (v Values) Ints() (vals []int, err error) { + for _, s := range v { + f, err := s.Int() + if err != nil { + return nil, err + } + vals = append(vals, f) + } + return vals, err +} + +func (v Values) Strings() (vals []string) { + for _, s := range v { + vals = append(vals, s.String()) + } + return vals +} + +func Must[T any](v T, err error) T { + if err != nil { + panic(err) + } + return v +} + +func SafeMust[T any](v T, err error) T { + return v +} diff --git a/pkg/utils/signals/signal.go b/pkg/utils/signals/signal.go new file mode 100644 index 0000000..3fddf14 --- /dev/null +++ b/pkg/utils/signals/signal.go @@ -0,0 +1,71 @@ +package signals + +import ( + "fmt" + "os" + "os/signal" + "sync" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +type StopChan struct { + stopCh chan struct{} + reqWg sync.WaitGroup + wg sync.WaitGroup +} + +var once = sync.Once{} + +func (s *StopChan) WaitRequest() { + s.reqWg.Wait() +} + +func (s *StopChan) DoneRequest() { + s.reqWg.Done() +} + +func (s *StopChan) AddRequest(delta int) { + s.reqWg.Add(delta) +} + +func (s *StopChan) Wait() { + s.wg.Wait() +} + +func (s *StopChan) Done() { + s.wg.Done() +} + +func (s *StopChan) Add(delta int) { + s.wg.Add(delta) +} + +func (s *StopChan) Channel() <-chan struct{} { + return s.stopCh +} + +var stopChan *StopChan + +func SetupSignalHandler(logger log.Logger) (stopCh *StopChan) { + once.Do(func() { + onlyOneSignalHandler := make(chan struct{}) + close(onlyOneSignalHandler) // panics when called twice + stopChan = &StopChan{ + stopCh: make(chan struct{}), + } + c := make(chan os.Signal, 2) + signal.Notify(c, shutdownSignals...) + + go func() { + sig := <-c + level.Info(logger).Log("msg", fmt.Sprintf("收到信号[%s],进程停止\n", sig)) + close(stopChan.stopCh) + stopChan.WaitRequest() + stopChan.Wait() + os.Exit(1) // second signal. Exit directly. + }() + }) + return stopChan +} diff --git a/pkg/utils/signals/signal_posix.go b/pkg/utils/signals/signal_posix.go new file mode 100644 index 0000000..421d930 --- /dev/null +++ b/pkg/utils/signals/signal_posix.go @@ -0,0 +1,10 @@ +//go:build !windows + +package signals + +import ( + "os" + "syscall" +) + +var shutdownSignals = []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT} diff --git a/pkg/utils/signals/signal_windows.go b/pkg/utils/signals/signal_windows.go new file mode 100644 index 0000000..f2015c1 --- /dev/null +++ b/pkg/utils/signals/signal_windows.go @@ -0,0 +1,7 @@ +package signals + +import ( + "os" +) + +var shutdownSignals = []os.Signal{os.Interrupt} diff --git a/scripts/errcheck_excludes.txt b/scripts/errcheck_excludes.txt new file mode 100644 index 0000000..aecdaf1 --- /dev/null +++ b/scripts/errcheck_excludes.txt @@ -0,0 +1,10 @@ +// Don't flag lines such as "io.Copy(ioutil.Discard, resp.Body)". +io.Copy +// The next two are used in HTTP handlers, any error is handled by the server itself. +io.WriteString +(net/http.ResponseWriter).Write +// No need to check for errors on server's shutdown. +(*net/http.Server).Shutdown + +// Never check for logger errors. +(github.com/go-kit/log.Logger).Log