diff --git a/.env.sample b/.env.sample index d3a7367f2..4ab77182e 100644 --- a/.env.sample +++ b/.env.sample @@ -1,24 +1,15 @@ MESSAGING_HTTP_ADDR=:3000 -MESSAGING_HTTP_PRETTY_JSON=1 -MESSAGING_HTTP_ERROR_TRACING=1 -MESSAGING_DB_DSN=crust:crust@tcp(localhost:3306)/crust?collation=utf8mb4_general_ci +MESSAGING_DB_DSN=corteza:corteza@tcp(localhost:3306)/corteza?collation=utf8mb4_general_ci MESSAGING_DB_PROFILER=stdout COMPOSE_HTTP_ADDR=:3001 -COMPOSE_HTTP_PRETTY_JSON=1 -COMPOSE_HTTP_ERROR_TRACING=1 -COMPOSE_DB_DSN=crust:crust@tcp(localhost:3306)/crust?collation=utf8mb4_general_ci +COMPOSE_DB_DSN=corteza:corteza@tcp(localhost:3306)/corteza?collation=utf8mb4_general_ci COMPOSE_DB_PROFILER=stdout SYSTEM_HTTP_ADDR=:3002 -SYSTEM_HTTP_PRETTY_JSON=1 -SYSTEM_HTTP_ERROR_TRACING=1 -SYSTEM_DB_DSN=crust:crust@tcp(localhost:3306)/crust?collation=utf8mb4_general_ci +SYSTEM_DB_DSN=corteza:corteza@tcp(localhost:3306)/corteza?collation=utf8mb4_general_ci SYSTEM_DB_PROFILER=stdout -METRICS=1 -METRICS_PASSWORD=metrics - AUTH_JWT_SECRET=jwt-secret AUTH_JWT_DEBUG=1 @@ -26,7 +17,4 @@ AUTH_JWT_DEBUG=1 SMTP_HOST=localhost:1025 SMTP_USER= SMTP_PASS= -SMTP_FROM="Crust" - -SUBSCRIPTION_KEY=E7ox7cDMmBzsFS15Ub43KKdbBg6gqOYiUhK3nRN0BlpNzt88mHLycahhVfrJCccc -SUBSCRIPTION_DOMAIN=local.crust.tech +SMTP_FROM="Corteza" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a18a82ece..659fa8672 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,7 +7,7 @@ assignees: '' --- -Thanks for helping us to improve Crust! We welcome all bug reports. Once we've verified and fixed your issue, we'll ping you in the comments to see if you can verify the fix. +Thanks for helping us to improve Corteza! We welcome all bug reports. Once we've verified and fixed your issue, we'll ping you in the comments to see if you can verify the fix. We'll give you details on what version can be used to test the fix. Additionally, if you are interested in testing fixes that you ***didn't*** report, look for the issues with the `status/to-test` label. You can pick any of these up for verification. ***You can delete this message portion of the bug report.*** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index d63940bf2..d8ffd5f53 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,7 +7,7 @@ assignees: '' --- -Thanks for helping us to improve Crust! We welcome all feature requests. Once we've addressed your feature request, we'll ping you in the comments to see if you can verify the change. +Thanks for helping us to improve Corteza! We welcome all feature requests. Once we've addressed your feature request, we'll ping you in the comments to see if you can verify the change. We'll give you details on what version can be used to test the change. Additionally, if you are interested in testing other fixes or feature requests that you ***didn't*** report, look for the issues with the `status/to-test` label. You can pick any of these up for verification. ***You can delete this message portion of the bug report.*** diff --git a/.project b/.project index 5ad27bfa1..0d7884845 100644 --- a/.project +++ b/.project @@ -1 +1 @@ -crusttech/crust \ No newline at end of file +cortezaproject/corteza diff --git a/Gopkg.lock b/Gopkg.lock index 99f24f4c1..2b50fde3b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -44,14 +44,6 @@ pruneopts = "UT" revision = "982855dad3e1cd68828d2106997539f36fcf6601" -[[projects]] - branch = "master" - digest = "1:b2d8bf10f6279453f6771158d7afcbb2e047714f7d2e2204f2ea9d630d90d4f2" - name = "github.com/crusttech/permit" - packages = ["pkg/permit"] - pruneopts = "UT" - revision = "6c0c4bece8da7f75a52930f3c07d01a5406499bb" - [[projects]] digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" name = "github.com/davecgh/go-spew" @@ -305,11 +297,12 @@ revision = "1555304b9b35fdd2b425bccf1a5613677705e7d0" [[projects]] - digest = "1:75d51eeab0df85a3cea9e1297ccd3183b20a10cb4b48c753d8ec8d113cc14250" + digest = "1:93a746f1060a8acbcf69344862b2ceced80f854170e1caae089b2834c5fbf7f4" name = "github.com/prometheus/client_golang" packages = [ "prometheus", "prometheus/internal", + "prometheus/promhttp", ] pruneopts = "UT" revision = "505eaef017263e299324067d40ca2c48f6a2cf50" @@ -373,6 +366,14 @@ revision = "588a75ec4f32903aa5e39a2619ba6a4631e28424" version = "v1.2.2" +[[projects]] + digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc" + name = "github.com/spf13/cast" + packages = ["."] + pruneopts = "UT" + revision = "8c9545af88b134710ab1cd196795e7f2388358d7" + version = "v1.3.0" + [[projects]] digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939" name = "github.com/spf13/cobra" @@ -568,7 +569,6 @@ "github.com/SentimensRG/ctx", "github.com/SentimensRG/ctx/sigctx", "github.com/crusttech/go-oidc", - "github.com/crusttech/permit/pkg/permit", "github.com/davecgh/go-spew/spew", "github.com/dgrijalva/jwt-go", "github.com/disintegration/imaging", @@ -594,8 +594,9 @@ "github.com/markbates/goth/providers/openidConnect", "github.com/namsral/flag", "github.com/pkg/errors", - "github.com/prometheus/client_golang/prometheus", + "github.com/prometheus/client_golang/prometheus/promhttp", "github.com/spf13/afero", + "github.com/spf13/cast", "github.com/spf13/cobra", "github.com/titpetric/factory", "github.com/titpetric/factory/resputil", diff --git a/cmd/compose/main.go b/cmd/compose/main.go index d93ba1138..4dc9da150 100644 --- a/cmd/compose/main.go +++ b/cmd/compose/main.go @@ -1,69 +1,17 @@ package main import ( + "fmt" "os" - context "github.com/SentimensRG/ctx" - "github.com/SentimensRG/ctx/sigctx" - _ "github.com/joho/godotenv/autoload" - "github.com/namsral/flag" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - compose "github.com/crusttech/crust/compose" - "github.com/crusttech/crust/internal/logger" - "github.com/crusttech/crust/internal/subscription" - system "github.com/crusttech/crust/system" + "github.com/cortezaproject/corteza-server/compose" + "github.com/cortezaproject/corteza-server/pkg/cli" ) func main() { - // Initialize default logger - logger.Init(zapcore.DebugLevel) - log := logger.Default().Named("compose") - - // New signal-bond context that we will use and - // will get terminated (Done()) on SIGINT or SIGTERM - ctx := context.AsContext(sigctx.New()) - - // Bind default logger to context - ctx = logger.ContextWithValue(ctx, log) - - compose.Flags("compose") - system.Flags("system") - - subscription.Flags() - - flag.Parse() - - if err := system.Init(ctx); err != nil { - log.Fatal("failed to initialize system", zap.Error(err)) - } - if err := compose.Init(ctx); err != nil { - log.Fatal("failed to initialize compose", zap.Error(err)) - } - - var command string - if len(os.Args) > 1 { - command = os.Args[1] - } - - switch command { - case "help": - flag.PrintDefaults() - case "provision": - if err := compose.Provision(ctx); err != nil { - println("Failed to provision compose: ", err.Error()) - os.Exit(1) - } - default: - // Checks subscription, will os.Exit(1) if there is an error - // Disabled for now, system service is the only one that validates subscription - // ctx = subscription.Monitor(ctx) - - compose.StartWatchers(ctx) - - if err := compose.StartRestAPI(ctx); err != nil { - log.Fatal("failed to start compose REST API", zap.Error(err)) - } + c := compose.InitCompose() + if err := c.Command(cli.Context()).Execute(); err != nil { + fmt.Println(err) + os.Exit(1) } } diff --git a/cmd/crust/main.go b/cmd/crust/main.go deleted file mode 100644 index 11257b20b..000000000 --- a/cmd/crust/main.go +++ /dev/null @@ -1,159 +0,0 @@ -package main - -import ( - "net" - "net/http" - "os" - "path" - - context "github.com/SentimensRG/ctx" - "github.com/SentimensRG/ctx/sigctx" - "github.com/go-chi/chi" - _ "github.com/joho/godotenv/autoload" - "github.com/namsral/flag" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - compose "github.com/crusttech/crust/compose" - "github.com/crusttech/crust/internal/auth" - "github.com/crusttech/crust/internal/logger" - messaging "github.com/crusttech/crust/messaging" - system "github.com/crusttech/crust/system" - - "github.com/crusttech/crust/internal/config" - "github.com/crusttech/crust/internal/metrics" - "github.com/crusttech/crust/internal/middleware" - "github.com/crusttech/crust/internal/subscription" -) - -// Serves index.html in case the requested file isn't found (or some other os.Stat error) -func serveIndex(assetPath string, indexPath string, serve http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - indexPage := path.Join(assetPath, indexPath) - requestedPage := path.Join(assetPath, r.URL.Path) - _, err := os.Stat(requestedPage) - if err != nil { - http.ServeFile(w, r, indexPage) - return - } - serve.ServeHTTP(w, r) - } -} - -func main() { - // Initialize default logger - logger.Init(zapcore.DebugLevel) - log := logger.Default().Named("crust") - - // New signal-bond context that we will use and - // will get terminated (Done()) on SIGINT or SIGTERM - ctx := context.AsContext(sigctx.New()) - - // Bind default logger to context - ctx = logger.ContextWithValue(ctx, log) - - var flags struct { - http *config.HTTP - monitor *config.Monitor - } - - flags.http = new(config.HTTP).Init() - flags.monitor = new(config.Monitor).Init() - - compose.Flags("compose") - messaging.Flags("messaging") - system.Flags("system") - - authJwtFlags := new(config.JWT).Init() - - subscription.Flags() - - flag.Parse() - - var command string - if len(os.Args) > 1 { - command = os.Args[1] - } - - switch command { - case "help": - flag.PrintDefaults() - - case "provision": - if err := system.Provision(ctx); err != nil { - println("Failed to provision system: ", err.Error()) - os.Exit(1) - } - if err := compose.Provision(ctx); err != nil { - println("Failed to provision compose: ", err.Error()) - os.Exit(1) - } - if err := messaging.Provision(ctx); err != nil { - println("Failed to provision messaging: ", err.Error()) - os.Exit(1) - } - default: - // Initialize configuration of our services - if err := system.Init(ctx); err != nil { - log.Fatal("failed to initialize system", zap.Error(err)) - } - if err := compose.Init(ctx); err != nil { - log.Fatal("failed to initialize compose", zap.Error(err)) - } - if err := messaging.Init(ctx); err != nil { - log.Fatal("failed to initialize messaging", zap.Error(err)) - } - // Checks subscription, will os.Exit(1) if there is an error - // Disabled for now, system service is the only one that validates subscription - // ctx = subscription.Monitor(ctx) - - log.Info("Starting http server on address " + flags.http.Addr) - listener, err := net.Listen("tcp", flags.http.Addr) - if err != nil { - log.Info("Can't listen on addr " + flags.http.Addr) - } - - if flags.monitor.Interval > 0 { - go metrics.NewMonitor(flags.monitor.Interval) - } - - system.StartWatchers(ctx) - compose.StartWatchers(ctx) - messaging.StartWatchers(ctx) - - r := chi.NewRouter() - - // logging, cors and such - middleware.Mount(ctx, r, flags.http) - - // Use JWT secret for hmac signer for now - auth.DefaultSigner = auth.HmacSigner(authJwtFlags.Secret) - auth.DefaultJwtHandler, err = auth.JWT(authJwtFlags.Secret, authJwtFlags.Expiry) - if err != nil { - log.Fatal("Error creating JWT Auth", zap.Error(err)) - } - - r.Route("/api", func(r chi.Router) { - r.Route("/compose", func(r chi.Router) { - compose.MountRoutes(ctx, r) - }) - r.Route("/messaging", func(r chi.Router) { - messaging.MountRoutes(ctx, r) - }) - r.Route("/system", func(r chi.Router) { - system.MountRoutes(ctx, r) - }) - middleware.MountSystemRoutes(ctx, r, flags.http) - }) - - fileserver := http.FileServer(http.Dir("webapp")) - - for _, service := range []string{"admin", "auth", "messaging", "compose"} { - r.HandleFunc("/"+service+"*", serveIndex("webapp", "compose/index.html", fileserver)) - } - r.HandleFunc("/*", serveIndex("webapp", "index.html", fileserver)) - - go http.Serve(listener, r) - <-ctx.Done() - } -} diff --git a/cmd/messaging/main.go b/cmd/messaging/main.go index d20d43a0a..6e6409d4d 100644 --- a/cmd/messaging/main.go +++ b/cmd/messaging/main.go @@ -1,68 +1,17 @@ package main import ( + "fmt" "os" - context "github.com/SentimensRG/ctx" - "github.com/SentimensRG/ctx/sigctx" - _ "github.com/joho/godotenv/autoload" - "github.com/namsral/flag" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - "github.com/crusttech/crust/internal/logger" - "github.com/crusttech/crust/internal/subscription" - messaging "github.com/crusttech/crust/messaging" - system "github.com/crusttech/crust/system" + "github.com/cortezaproject/corteza-server/messaging" + "github.com/cortezaproject/corteza-server/pkg/cli" ) func main() { - // Initialize default logger - logger.Init(zapcore.DebugLevel) - log := logger.Default().Named("messaging") - - // New signal-bond context that we will use and - // will get terminated (Done()) on SIGINT or SIGTERM - ctx := context.AsContext(sigctx.New()) - - // Bind default logger to context - ctx = logger.ContextWithValue(ctx, log) - - messaging.Flags("messaging") - system.Flags("system") - - subscription.Flags() - - flag.Parse() - - if err := system.Init(ctx); err != nil { - log.Fatal("failed to initialize system", zap.Error(err)) - } - if err := messaging.Init(ctx); err != nil { - log.Fatal("failed to initialize messaging", zap.Error(err)) - } - - var command string - if len(os.Args) > 1 { - command = os.Args[1] - } - - switch command { - case "help": - flag.PrintDefaults() - - case "provision": - if err := messaging.Provision(ctx); err != nil { - println("Failed to provision messagign: ", err.Error()) - os.Exit(1) - } - default: - // Checks subscription, will os.Exit(1) if there is an error - // Disabled for now, system service is the only one that validates subscription - // ctx = subscription.Monitor(ctx) - - if err := messaging.StartRestAPI(ctx); err != nil { - log.Fatal("failed to start messaging REST API", zap.Error(err)) - } + m := messaging.InitMessaging() + if err := m.Command(cli.Context()).Execute(); err != nil { + fmt.Println(err) + os.Exit(1) } } diff --git a/cmd/monolith/main.go b/cmd/monolith/main.go new file mode 100644 index 000000000..1b974df57 --- /dev/null +++ b/cmd/monolith/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + "os" + + "github.com/cortezaproject/corteza-server/monolith" + "github.com/cortezaproject/corteza-server/pkg/cli" +) + +func main() { + c := monolith.InitMonolith() + if err := c.Command(cli.Context()).Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/system-cli/flags.go b/cmd/system-cli/flags.go deleted file mode 100644 index d074ab67c..000000000 --- a/cmd/system-cli/flags.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import ( - _ "github.com/joho/godotenv/autoload" - "github.com/namsral/flag" -) - -func flags(prefix string, mountFlags ...func(...string)) { - for _, mount := range mountFlags { - mount(prefix) - } - flag.Parse() -} diff --git a/cmd/system-cli/main.go b/cmd/system-cli/main.go deleted file mode 100644 index 22ce82187..000000000 --- a/cmd/system-cli/main.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - context "github.com/SentimensRG/ctx" - "github.com/SentimensRG/ctx/sigctx" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - "github.com/crusttech/crust/internal/logger" - system "github.com/crusttech/crust/system" - "github.com/crusttech/crust/system/cli" -) - -func main() { - - // Initialize default logger - logger.Init(zapcore.DebugLevel) - log := logger.Default().Named("system-cli") - - // New signal-bond context that we will use and - // will get terminated (Done()) on SIGINT or SIGTERM - ctx := context.AsContext(sigctx.New()) - - // Bind default logger to context - ctx = logger.ContextWithValue(ctx, log) - - flags("system", system.Flags) - if err := system.Init(ctx); err != nil { - log.Fatal("failed to initialize system", zap.Error(err)) - } - - cli.StartCLI(ctx) -} diff --git a/cmd/system/main.go b/cmd/system/main.go index e15ad4ee9..3c4265938 100644 --- a/cmd/system/main.go +++ b/cmd/system/main.go @@ -1,62 +1,17 @@ package main import ( + "fmt" "os" - context "github.com/SentimensRG/ctx" - "github.com/SentimensRG/ctx/sigctx" - _ "github.com/joho/godotenv/autoload" - "github.com/namsral/flag" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - - "github.com/crusttech/crust/internal/logger" - "github.com/crusttech/crust/internal/subscription" - system "github.com/crusttech/crust/system" + "github.com/cortezaproject/corteza-server/pkg/cli" + "github.com/cortezaproject/corteza-server/system" ) func main() { - - // Initialize default logger - logger.Init(zapcore.DebugLevel) - log := logger.Default().Named("system") - - // New signal-bond context that we will use and - // will get terminated (Done()) on SIGINT or SIGTERM - ctx := context.AsContext(sigctx.New()) - - // Bind default logger to context - ctx = logger.ContextWithValue(ctx, log) - - system.Flags("system") - - subscription.Flags() - - flag.Parse() - - if err := system.Init(ctx); err != nil { - log.Fatal("failed to initialize system", zap.Error(err)) - } - - var command string - if len(os.Args) > 1 { - command = os.Args[1] - } - - switch command { - case "help": - flag.PrintDefaults() - case "provision": - if err := system.Provision(ctx); err != nil { - println("Failed to provision system: ", err.Error()) - os.Exit(1) - } - default: - // Checks subscription, will os.Exit(1) if there is an error - ctx = subscription.Monitor(ctx) - - if err := system.StartRestAPI(ctx); err != nil { - log.Fatal("failed to start system REST API", zap.Error(err)) - } + s := system.InitSystem() + if err := s.Command(cli.Context()).Execute(); err != nil { + fmt.Println(err) + os.Exit(1) } } diff --git a/internal/auth/error.go b/internal/auth/error.go index 0aec39d9d..55921d80c 100644 --- a/internal/auth/error.go +++ b/internal/auth/error.go @@ -17,7 +17,7 @@ func (e authError) Error() string { } func (e authError) String() string { - return "crust.internal.auth." + string(e) + return "internal.auth." + string(e) } func (e authError) New() error { diff --git a/internal/auth/jwt.go b/internal/auth/jwt.go index aa4128979..55a12584d 100644 --- a/internal/auth/jwt.go +++ b/internal/auth/jwt.go @@ -17,16 +17,19 @@ type ( expiry int64 tokenAuth *jwtauth.JWTAuth } - - jwtSettingsGetter interface { - GetGlobalString(name string) (out string, err error) - } ) var ( DefaultJwtHandler TokenHandler ) +func SetupDefault(secret string, expiry int) { + // Use JWT secret for hmac signer for now + DefaultSigner = HmacSigner(secret) + DefaultJwtHandler, _ = JWT(secret, int64(expiry)) + +} + func JWT(secret string, expiry int64) (jwt *token, err error) { if len(secret) == 0 { return nil, errors.New("JWT secret missing") diff --git a/internal/config/database.go b/internal/config/database.go deleted file mode 100644 index 32330b29d..000000000 --- a/internal/config/database.go +++ /dev/null @@ -1,53 +0,0 @@ -package config - -import ( - "github.com/namsral/flag" - "github.com/pkg/errors" -) - -type ( - Database struct { - DSN string - Profiler string - } -) - -var dbs map[string]*Database - -func (c *Database) Validate() error { - if c == nil { - return nil - } - if c.DSN == "" { - return errors.New("No DB DSN is set, can't connect to database") - } - return nil -} - -func (*Database) Init(prefix ...string) *Database { - if dbs == nil { - dbs = make(map[string]*Database) - } - - name := "default" - if len(prefix) > 0 { - name = prefix[0] - } - - if db := dbs[name]; db != nil { - return db - } - - p := func(s string) string { - if len(prefix) > 0 { - return prefix[0] + "-" + s - } - return s - } - - db := new(Database) - flag.StringVar(&db.DSN, p("db-dsn"), "", "DSN for database connection (e.g. user:pass@tcp(db1:3306)/dbname?collation=utf8mb4_general_ci)") - flag.StringVar(&db.Profiler, p("db-profiler"), "", "Profiler for DB queries (none, stdout)") - dbs[name] = db - return db -} diff --git a/internal/config/http.go b/internal/config/http.go deleted file mode 100644 index 194221caf..000000000 --- a/internal/config/http.go +++ /dev/null @@ -1,61 +0,0 @@ -package config - -import ( - "github.com/namsral/flag" - "github.com/pkg/errors" -) - -type ( - HTTP struct { - Addr string - Logging bool - Pretty bool - Tracing bool - Metrics bool - - ClientTSLInsecure bool - - MetricsUsername, MetricsPassword string - } -) - -var http *HTTP - -func (c *HTTP) Validate() error { - if c == nil { - return nil - } - if c.Addr == "" { - return errors.New("No HTTP Addr is set, can't listen for HTTP") - } - if c.Metrics && (c.MetricsUsername == "" || c.MetricsPassword == "") { - return errors.New("We can't have unprotected /metrics, please set METRICS_USERNAME/PASSWORD") - } - return nil -} - -func (*HTTP) Init(prefix ...string) *HTTP { - if http != nil { - return http - } - - p := func(s string) string { - if len(prefix) > 0 { - return prefix[0] + "-" + s - } - return s - } - - http = new(HTTP) - flag.StringVar(&http.Addr, p("http-addr"), ":80", "Listen address for HTTP server") - flag.BoolVar(&http.Logging, p("http-log"), true, "Enable/disable HTTP request log") - flag.BoolVar(&http.Pretty, p("http-pretty-json"), false, "Prettify returned JSON output") - flag.BoolVar(&http.Tracing, p("http-error-tracing"), false, "Return error stack frame") - - flag.BoolVar(&http.ClientTSLInsecure, p("http-client-tsl-insecure"), false, "Skip insecure TSL verification on outbound HTTP requests (allow invalid/self-signed certificates)") - - flag.BoolVar(&http.Metrics, "metrics", false, "Provide metrics export for prometheus") - flag.StringVar(&http.MetricsUsername, "metrics-username", "metrics", "Provide metrics export for prometheus") - flag.StringVar(&http.MetricsPassword, "metrics-password", "", "Provide metrics export for prometheus") - return http -} diff --git a/internal/config/http_client.go b/internal/config/http_client.go deleted file mode 100644 index bd740d237..000000000 --- a/internal/config/http_client.go +++ /dev/null @@ -1,28 +0,0 @@ -package config - -import ( - "github.com/namsral/flag" -) - -type ( - HTTPClient struct { - BaseURL string - Timeout int - } -) - -var httpClient *HTTPClient - -func (c *HTTPClient) Validate() error { - return nil -} - -func (*HTTPClient) Init(prefix ...string) *HTTPClient { - if httpClient != nil { - return httpClient - } - httpClient = new(HTTPClient) - flag.StringVar(&httpClient.BaseURL, "http-client-base-url", "", "HTTP Client Base URL") - flag.IntVar(&httpClient.Timeout, "http-client-timeout", 5, "HTTP Client request timeout (seconds)") - return httpClient -} diff --git a/internal/config/jwt.go b/internal/config/jwt.go deleted file mode 100644 index d437b6a6b..000000000 --- a/internal/config/jwt.go +++ /dev/null @@ -1,36 +0,0 @@ -package config - -import ( - "github.com/namsral/flag" - "github.com/pkg/errors" -) - -type ( - JWT struct { - Secret string - Expiry int64 - } -) - -var jwt *JWT - -func (c *JWT) Validate() error { - if c == nil { - return nil - } - if c.Secret == "" { - return errors.New("JWT Secret not set for AUTH") - } - return nil -} - -func (*JWT) Init(prefix ...string) *JWT { - if jwt != nil { - return jwt - } - - jwt = new(JWT) - flag.StringVar(&jwt.Secret, "auth-jwt-secret", "", "JWT Secret") - flag.Int64Var(&jwt.Expiry, "auth-jwt-expiry", 60*24*30, "JWT Expiration in minutes") - return jwt -} diff --git a/internal/config/messaging_settings.go b/internal/config/messaging_settings.go deleted file mode 100644 index 42e6bb930..000000000 --- a/internal/config/messaging_settings.go +++ /dev/null @@ -1,57 +0,0 @@ -package config - -// @todo need to decide on settings format & structure... -// type ( -// // MessagingSettings holds configuration settings for mesaging service -// MessagingSettings struct { -// Messages struct { -// Body struct { -// // How long can a message be -// MaxLength uint `json:"maxLength,omitempty"` -// -// // On render, convert textual [:)] emoji to graph. empji -// EmojiConvertToPicture bool `json:"emojiConvertToPicture,omitempty"` -// } `json:"body,omitempty"` -// -// Avatars struct { -// // Display image that users use for the avatar -// // if false, it will use initials only -// DisplaySelectedImage bool `json:"displaySelectedImage,omitempty"` -// -// // Enable avatar -// Enabled bool `json:"enabled,omitempty"` -// } `json:"avatars,omitempty"` -// } `json:"messages,omitempty"` -// -// Channels struct { -// Name struct { -// // How long can a name be -// MaxLength uint `json:"maxLength,omitempty"` -// -// // Can we have spaces in channel's name -// AllowSpaces bool `json:"allowSpaces,omitempty"` -// } `json:"name,omitempty"` -// -// Topic struct { -// // How long can a name be -// MaxLength uint `json:"maxLength,omitempty"` -// -// // Can we have spaces in channel's name -// Enabled bool `json:"enabled,omitempty"` -// } `json:"topic,omitempty"` -// } `json:"channels,omitempty"` -// } -// ) -// -// func DefaultMessagingSettings() (s MessagingSettings) { -// s.Messages.Body.MaxLength = 10000 -// s.Messages.Body.EmojiConvertToPicture = true -// s.Messages.Avatars.DisplaySelectedImage = true -// s.Messages.Avatars.Enabled = true -// s.Channels.Name.MaxLength = 40 -// s.Channels.Name.AllowSpaces = true -// s.Channels.Topic.MaxLength = 200 -// s.Channels.Topic.Enabled = true -// -// return s -// } diff --git a/internal/config/monitor.go b/internal/config/monitor.go deleted file mode 100644 index 598720363..000000000 --- a/internal/config/monitor.go +++ /dev/null @@ -1,27 +0,0 @@ -package config - -import ( - "github.com/namsral/flag" -) - -type ( - Monitor struct { - Interval int - } -) - -var monitor *Monitor - -func (c *Monitor) Validate() error { - return nil -} - -func (*Monitor) Init(prefix ...string) *Monitor { - if monitor != nil { - return monitor - } - - monitor = new(Monitor) - flag.IntVar(&monitor.Interval, "monitor-interval", 300, "Monitor interval (seconds, 0 = disable)") - return monitor -} diff --git a/internal/config/pubsub.go b/internal/config/pubsub.go deleted file mode 100644 index e2051b220..000000000 --- a/internal/config/pubsub.go +++ /dev/null @@ -1,55 +0,0 @@ -package config - -import ( - "time" - - "github.com/namsral/flag" - "github.com/pkg/errors" - - "github.com/crusttech/crust/internal/logger" -) - -type ( - PubSub struct { - Mode string - RedisAddr string - PollingInterval time.Duration - - Timeout time.Duration - PingTimeout time.Duration - PingPeriod time.Duration - } -) - -var pubsub *PubSub - -func (c *PubSub) Validate() error { - switch c.Mode { - case "redis": - if c.Mode == "redis" && c.RedisAddr == "" { - logger.Default().Info("[pubsub] No Redis Address defined for mode=redis, falling back to polling") - c.Mode = "poll" - } - case "poll": - default: - return errors.Errorf("Unknown PubSub.Mode: %s", c.Mode) - } - return nil -} - -func (*PubSub) Init(prefix ...string) *PubSub { - if pubsub != nil { - return pubsub - } - - pubsub = new(PubSub) - pubsub.Timeout = 15 * time.Second - pubsub.PingTimeout = 60 * time.Second - pubsub.PingPeriod = (pubsub.PingTimeout * 10) / 9 - - flag.StringVar(&pubsub.Mode, "pubsub", "poll", "Pubsub mode (poll, redis)") - flag.StringVar(&pubsub.RedisAddr, "pubsub-redis", "", "Redis Pub/Sub hostname") - flag.DurationVar(&pubsub.PollingInterval, "pubsub-poll-interval", 3*time.Second, "Pub/Sub polling interval (3s, 12m, 3h...)") - - return pubsub -} diff --git a/internal/config/smtp.go b/internal/config/smtp.go deleted file mode 100644 index 21c29769b..000000000 --- a/internal/config/smtp.go +++ /dev/null @@ -1,69 +0,0 @@ -package config - -import ( - "errors" - "strconv" - "strings" - - "github.com/namsral/flag" -) - -type ( - SMTP struct { - Host string - Port int - User string - Pass string - From string - } -) - -const defaultSMTPPort = 25 - -var smtp *SMTP - -// Validate -// -// No actual validation here for SMTP, we allow mis/un-configured -func (c *SMTP) Validate() error { - if strings.Contains(c.Host, ":") { - parts := strings.SplitN(c.Host, ":", 2) - c.Port, _ = strconv.Atoi(parts[1]) - c.Host = parts[0] - } - - return nil -} - -// RuntimeValidation is used for run-time configuration validation -func (c *SMTP) RuntimeValidation() error { - if c == nil { - return errors.New("SMTP config missing") - } - - if c.Host == "" { - return errors.New("No hostname provided for SMTP") - } - if c.Port == 0 { - return errors.New("No port provided for SMTP") - } - if c.From == "" { - return errors.New("Sender for SMTP is not set") - } - - return nil -} - -func (*SMTP) Init(prefix ...string) *SMTP { - if smtp != nil { - return smtp - } - smtp = new(SMTP) - flag.StringVar(&smtp.Host, "smtp-host", "", "SMTP hostname (may be host:port)") - flag.IntVar(&smtp.Port, "smtp-port", defaultSMTPPort, "SMTP port number") - flag.StringVar(&smtp.User, "smtp-user", "", "SMTP server username") - flag.StringVar(&smtp.Pass, "smtp-pass", "", "SMTP server password") - flag.StringVar(&smtp.From, "smtp-from", "", "SMTP sender header") - - return smtp -} diff --git a/internal/config/subscription.go b/internal/config/subscription.go deleted file mode 100644 index 8f248a107..000000000 --- a/internal/config/subscription.go +++ /dev/null @@ -1,33 +0,0 @@ -package config - -import ( - "github.com/namsral/flag" -) - -type ( - Subscription struct { - Key string - Domain string - } -) - -var subscription *Subscription - -func (c *Subscription) Validate() error { - if c == nil { - return nil - } - - return nil -} - -func (*Subscription) Init(prefix ...string) *Subscription { - if subscription != nil { - return subscription - } - - subscription = new(Subscription) - flag.StringVar(&subscription.Key, "subscription-key", "", "Subscription key") - flag.StringVar(&subscription.Domain, "subscription-domain", "", "Domain") - return subscription -} diff --git a/internal/config/websocket.go b/internal/config/websocket.go deleted file mode 100644 index a27cd46eb..000000000 --- a/internal/config/websocket.go +++ /dev/null @@ -1,30 +0,0 @@ -package config - -import ( - "time" -) - -type ( - Websocket struct { - Timeout time.Duration - PingTimeout time.Duration - PingPeriod time.Duration - } -) - -var websocket *Websocket - -func (c *Websocket) Validate() error { - return nil -} - -func (*Websocket) Init(prefix ...string) *Websocket { - if websocket != nil { - return websocket - } - websocket = new(Websocket) - websocket.Timeout = 15 * time.Second - websocket.PingTimeout = 120 * time.Second - websocket.PingPeriod = (websocket.PingTimeout * 9) / 10 - return websocket -} diff --git a/internal/db/connector.go b/internal/db/connector.go index 31a8cb571..89648b966 100644 --- a/internal/db/connector.go +++ b/internal/db/connector.go @@ -7,8 +7,6 @@ import ( "github.com/pkg/errors" "github.com/titpetric/factory" "go.uber.org/zap" - - "github.com/crusttech/crust/internal/logger" ) const ( @@ -17,17 +15,19 @@ const ( timeout = 1 * time.Minute ) -func TryToConnect(ctx context.Context, name, dsn, profiler string) (db *factory.DB, err error) { +func TryToConnect(ctx context.Context, log *zap.Logger, name, dsn, profiler string) (db *factory.DB, err error) { factory.Database.Add(name, dsn) var ( connErrCh = make(chan error, 1) - log = logger.Default().With(zap.String("name", name), zap.String("dsn", dsn)) ) + // This logger is also used inside profiler + log = log.Named("database").With(zap.String("name", name)) + defer close(connErrCh) - log.Debug("connecting to the database") + log.Debug("connecting to the database", zap.String("dsn", dsn)) go func() { var ( @@ -48,6 +48,7 @@ func TryToConnect(ctx context.Context, name, dsn, profiler string) (db *factory. "could not connect", zap.Error(err), zap.Int("try", try), + zap.String("dsn", dsn), zap.Float64("delay", delay.Seconds()), ) @@ -84,11 +85,9 @@ func TryToConnect(ctx context.Context, name, dsn, profiler string) (db *factory. case "stdout": db.Profiler = &factory.Database.ProfilerStdout case "logger": - db.Profiler = ZapProfiler(logger.Default(). - Named("database"). - // Skip 3 levels in call stack to get to the actual function used - WithOptions(zap.AddCallerSkip(3)). - With(zap.String("connection", name)), + // Skip 3 levels in call stack to get to the actual function used + db.Profiler = ZapProfiler(log. + WithOptions(zap.AddCallerSkip(3)), ) default: log.Info("no database query profiler selected") diff --git a/internal/http/client.go b/internal/http/client.go index c36fab941..563634554 100644 --- a/internal/http/client.go +++ b/internal/http/client.go @@ -12,17 +12,20 @@ import ( "time" "github.com/pkg/errors" - - "github.com/crusttech/crust/internal/config" ) type ( + Config struct { + BaseURL string + Timeout int + } + Client struct { Transport *http.Transport Client *http.Client debugLevel DebugLevel - config *config.HTTPClient + config *Config } Request http.Request @@ -32,14 +35,10 @@ type ( const ( INFO DebugLevel = "info" - FULL = "full" + FULL DebugLevel = "full" ) -func New(flags *config.HTTPClient) (*Client, error) { - if err := flags.Validate(); err != nil { - return nil, errors.Wrap(err, "creating http client failed") - } - +func New(flags *Config) (*Client, error) { timeout := time.Duration(flags.Timeout) * time.Second transport := &http.Transport{ @@ -49,6 +48,7 @@ func New(flags *config.HTTPClient) (*Client, error) { TLSHandshakeTimeout: timeout, } + // @todo migrate to http.DefaultClient & http.DefaultTransport, see internal/http.SetupDefaults client := &http.Client{ Timeout: timeout, Transport: transport, diff --git a/internal/http/client_fortune_test.go b/internal/http/client_fortune_test.go index 2811ea176..5dd44ede7 100644 --- a/internal/http/client_fortune_test.go +++ b/internal/http/client_fortune_test.go @@ -10,5 +10,5 @@ type Fortune struct{} func (*Fortune) ServeHTTP(w http.ResponseWriter, r *http.Request) { fortune := "Fortune favors the prepared mind. - Louis Pasteur" - fmt.Fprintf(w, fortune) + _, _ = fmt.Fprint(w, fortune) } diff --git a/internal/http/client_test.go b/internal/http/client_test.go index f52fdc2e8..589c6aff4 100644 --- a/internal/http/client_test.go +++ b/internal/http/client_test.go @@ -1,12 +1,10 @@ package http import ( + "net/http/httptest" "testing" - "net/http/httptest" - - "github.com/crusttech/crust/internal/config" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) func TestHTTPClient(t *testing.T) { @@ -14,7 +12,7 @@ func TestHTTPClient(t *testing.T) { server := httptest.NewServer(handler) defer server.Close() - client, err := New(&config.HTTPClient{ + client, err := New(&Config{ Timeout: 10, }) test.Assert(t, err == nil, "%+v", err) diff --git a/internal/http/default.go b/internal/http/default.go new file mode 100644 index 000000000..dda185aaa --- /dev/null +++ b/internal/http/default.go @@ -0,0 +1,34 @@ +package http + +import ( + "crypto/tls" + "net" + "net/http" + "time" +) + +// SetupDefaults Reconfigures defaults for HTTP client & transport +func SetupDefaults(timeout time.Duration, tslInsecure bool) { + if tslInsecure { + // This will allow HTTPS requests to insecure hosts (expired, wrong host, self signed, untrusted root...) + // With this enabled, features like OIDC auto-discovery should work on any of examples found on badssl.com. + // + // With SYSTEM_HTTP_CLIENT_TSL_INSECURE=0 (default) next command returns 404 error (expected) + // > ./system external-auth auto-discovery foo-tsl-1 https://expired.badssl.com/ + // + // Without SYSTEM_HTTP_CLIENT_TSL_INSECURE=1 next command returns "x509: certificate has expired or is not yet valid" + // > ./system external-auth auto-discovery foo-tsl-1 https://expired.badssl.com/ + // + http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + http.DefaultTransport.(*http.Transport).DialContext = (&net.Dialer{Timeout: timeout}).DialContext + http.DefaultTransport.(*http.Transport).TLSHandshakeTimeout = timeout + } + + if timeout > 0 { + http.DefaultClient.Timeout = timeout + } + + http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index a08e4f1e9..d3bea5d9c 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -6,14 +6,17 @@ import ( ) var ( + DefaultLevel = zap.NewAtomicLevel() defaultLogger *zap.Logger ) func Init(level zapcore.Level) { + DefaultLevel.SetLevel(level) + var ( err error conf = zap.Config{ - Level: zap.NewAtomicLevelAt(level), + Level: DefaultLevel, Development: false, Encoding: "json", EncoderConfig: zap.NewProductionEncoderConfig(), diff --git a/internal/mail/mail.go b/internal/mail/mail.go index 6772862f6..e9ae6178f 100644 --- a/internal/mail/mail.go +++ b/internal/mail/mail.go @@ -2,11 +2,11 @@ package mail import ( "regexp" + "strconv" + "strings" "github.com/pkg/errors" gomail "gopkg.in/mail.v2" - - "github.com/crusttech/crust/internal/config" ) type ( @@ -30,19 +30,40 @@ func init() { addressCheck = regexp.MustCompile(addressCheckRE) } -func SetupDialer(config *config.SMTP) { - defaultDialerError = config.RuntimeValidation() +// SetupDialer setups SMTP dialer +// +// Host variable can contain ":" that will override port value +func SetupDialer(host string, port int, user, pass, from string) { + if host == "" { + defaultDialerError = errors.New("No hostname provided for SMTP") + return + } + + if strings.Contains(host, ":") { + parts := strings.SplitN(host, ":", 2) + port, _ = strconv.Atoi(parts[1]) + host = parts[0] + } + + if port == 0 { + defaultDialerError = errors.New("No port provided for SMTP") + return + } + if from == "" { + defaultDialerError = errors.New("Sender for SMTP is not set") + return + } if defaultDialerError != nil { return } - defaultFrom = config.From + defaultFrom = from defaultDialer = gomail.NewDialer( - config.Host, - config.Port, - config.User, - config.Pass, + host, + port, + user, + pass, ) } diff --git a/internal/mail/mail_test.go b/internal/mail/mail_test.go index 857cf351b..1062088ac 100644 --- a/internal/mail/mail_test.go +++ b/internal/mail/mail_test.go @@ -6,15 +6,14 @@ import ( "github.com/golang/mock/gomock" - "github.com/crusttech/crust/internal/config" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) func TestDialerInvalidSetup(t *testing.T) { defaultDialer = nil defaultDialerError = nil - SetupDialer(nil) + SetupDialer("", 0, "", "", "") test.Assert(t, defaultDialerError != nil, "'Missing SMTP configuration' error should be set, got: %v", defaultDialerError) test.Assert(t, defaultDialer == nil, "defaultDialer should n be set, got: %v", defaultDialer) } @@ -23,13 +22,7 @@ func TestDialerValidSetup(t *testing.T) { defaultDialer = nil defaultDialerError = nil - cfg := &config.SMTP{ - Host: "localhost:321", - From: "some@email.tld", - } - cfg.Validate() - - SetupDialer(cfg) + SetupDialer("localhost:321", 0, "", "", "some@email.tld") test.Assert(t, defaultDialerError == nil, "defaultDialerError should be nil, got %v", defaultDialerError) test.Assert(t, defaultDialer != nil, "defaultDialer should be set, got %v", defaultDialer) diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go deleted file mode 100644 index 23471ba59..000000000 --- a/internal/metrics/metrics.go +++ /dev/null @@ -1,33 +0,0 @@ -package metrics - -import ( - "net/http" - - "github.com/766b/chi-prometheus" - "github.com/99designs/basicauth-go" - "github.com/go-chi/chi" - "github.com/prometheus/client_golang/prometheus" - - "github.com/crusttech/crust/internal/config" -) - -// Middleware is the request logger that provides metrics to prometheus -func Middleware(name string) func(http.Handler) http.Handler { - return chiprometheus.NewMiddleware(name) -} - -// Handler exports prometheus metrics for /metrics requests -func Handler() http.Handler { - return prometheus.Handler() -} - -func MountRoutes(r chi.Router, opts *config.HTTP) { - if opts.Metrics { - r.Group(func(r chi.Router) { - r.Use(basicauth.New("Metrics", map[string][]string{ - opts.MetricsUsername: {opts.MetricsPassword}, - })) - r.Handle("/metrics", Handler()) - }) - } -} diff --git a/internal/middleware/mount.go b/internal/middleware/mount.go deleted file mode 100644 index c9adef154..000000000 --- a/internal/middleware/mount.go +++ /dev/null @@ -1,58 +0,0 @@ -package middleware - -import ( - "context" - "fmt" - "net/http" - "reflect" - "runtime" - - "github.com/go-chi/chi" - "github.com/go-chi/chi/middleware" - - "github.com/crusttech/crust/internal/config" - "github.com/crusttech/crust/internal/logger" - "github.com/crusttech/crust/internal/metrics" - "github.com/crusttech/crust/internal/version" -) - -func Mount(ctx context.Context, r chi.Router, opts *config.HTTP) { - r.Use(handleCORS) - r.Use(middleware.RealIP) - r.Use(middleware.RequestID) - r.Use(ContextLogger(logger.ContextValue(ctx))) - - if opts.Logging { - r.Use(LogRequest) - r.Use(LogResponse) - } - - if opts.Metrics { - r.Use(metrics.Middleware("crust")) - } -} - -func MountSystemRoutes(ctx context.Context, r chi.Router, opts *config.HTTP) { - metrics.MountRoutes(r, opts) - r.Mount("/debug", middleware.Profiler()) - r.Get("/version", version.HttpHandler) - - r.Get("/routes", func(w http.ResponseWriter, req *http.Request) { - var printRoutes func(chi.Routes, string) - - printRoutes = func(r chi.Routes, pfix string) { - routes := r.Routes() - for _, route := range routes { - if route.SubRoutes != nil && len(route.SubRoutes.Routes()) > 0 { - printRoutes(route.SubRoutes, pfix+route.Pattern[:len(route.Pattern)-2]) - } else { - for method, fn := range route.Handlers { - fmt.Fprintf(w, "%-8s %-80s -> %s\n", method, pfix+route.Pattern, runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()) - } - } - } - } - - printRoutes(r, "") - }) -} diff --git a/internal/organization/organization.go b/internal/organization/organization.go index e310da78b..eb1ae2934 100644 --- a/internal/organization/organization.go +++ b/internal/organization/organization.go @@ -3,10 +3,10 @@ package organization import ( "context" - "github.com/crusttech/crust/system/types" + "github.com/cortezaproject/corteza-server/system/types" ) -func Crust() types.Organisation { +func Corteza() types.Organisation { return types.Organisation{ID: 1} } @@ -14,6 +14,6 @@ func GetFromContext(ctx context.Context) types.Organisation { if orgID, ok := ctx.Value("organizationID").(uint64); ok { return types.Organisation{ID: orgID} } else { - return Crust() + return Corteza() } } diff --git a/internal/payload/incoming.go b/internal/payload/incoming.go index 38b96b7e0..2f7b5519a 100644 --- a/internal/payload/incoming.go +++ b/internal/payload/incoming.go @@ -3,7 +3,7 @@ package payload import ( "encoding/json" - "github.com/crusttech/crust/internal/payload/incoming" + "github.com/cortezaproject/corteza-server/internal/payload/incoming" ) func Unmarshal(raw []byte) (*incoming.Payload, error) { diff --git a/internal/payload/outgoing.go b/internal/payload/outgoing.go index 875ead2c5..b15f17fe7 100644 --- a/internal/payload/outgoing.go +++ b/internal/payload/outgoing.go @@ -5,10 +5,10 @@ import ( "fmt" "net/url" - "github.com/crusttech/crust/internal/auth" - "github.com/crusttech/crust/internal/payload/outgoing" - messagingTypes "github.com/crusttech/crust/messaging/types" - systemTypes "github.com/crusttech/crust/system/types" + "github.com/cortezaproject/corteza-server/internal/auth" + "github.com/cortezaproject/corteza-server/internal/payload/outgoing" + messagingTypes "github.com/cortezaproject/corteza-server/messaging/types" + systemTypes "github.com/cortezaproject/corteza-server/system/types" ) const ( diff --git a/internal/permissions/resource.gen_test.go b/internal/permissions/resource.gen_test.go index 49b4d2d1d..777234384 100644 --- a/internal/permissions/resource.gen_test.go +++ b/internal/permissions/resource.gen_test.go @@ -5,7 +5,7 @@ import ( "errors" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) // Hello! This file is auto-generated. diff --git a/internal/permissions/resource_test.go b/internal/permissions/resource_test.go index a639c8d62..d51a51245 100644 --- a/internal/permissions/resource_test.go +++ b/internal/permissions/resource_test.go @@ -3,7 +3,7 @@ package permissions import ( "testing" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) func TestResource(t *testing.T) { diff --git a/internal/permissions/rule.gen_test.go b/internal/permissions/rule.gen_test.go index d51639c3e..f546231e2 100644 --- a/internal/permissions/rule.gen_test.go +++ b/internal/permissions/rule.gen_test.go @@ -5,7 +5,7 @@ import ( "errors" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) // Hello! This file is auto-generated. diff --git a/internal/permissions/ruleset_checks_test.go b/internal/permissions/ruleset_checks_test.go index cdfcdbea3..f53977a6e 100644 --- a/internal/permissions/ruleset_checks_test.go +++ b/internal/permissions/ruleset_checks_test.go @@ -3,7 +3,7 @@ package permissions import ( "testing" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) const ( diff --git a/internal/permissions/ruleset_utils_test.go b/internal/permissions/ruleset_utils_test.go index 6fc6df948..6491f67a4 100644 --- a/internal/permissions/ruleset_utils_test.go +++ b/internal/permissions/ruleset_utils_test.go @@ -4,7 +4,7 @@ import ( "reflect" "testing" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) // Test role inheritance diff --git a/internal/permissions/service.go b/internal/permissions/service.go index 8950d52d3..f4462df4b 100644 --- a/internal/permissions/service.go +++ b/internal/permissions/service.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" - "github.com/crusttech/crust/internal/auth" + "github.com/cortezaproject/corteza-server/internal/auth" ) type ( @@ -34,7 +34,7 @@ const ( // It acts as a caching layer func Service(ctx context.Context, logger *zap.Logger, repository *repository) (svc *service) { svc = &service{ - f: make(chan bool, 0), + f: make(chan bool), logger: logger.Named("permissions"), repository: repository, @@ -117,7 +117,7 @@ func (svc service) Watch(ctx context.Context) { for { select { case <-ctx.Done(): - break + return case <-ticker.C: svc.Reload(ctx) case <-svc.f: diff --git a/internal/settings/types.gen_test.go b/internal/settings/types.gen_test.go index 71b2d41f1..87e1b53fb 100644 --- a/internal/settings/types.gen_test.go +++ b/internal/settings/types.gen_test.go @@ -5,7 +5,7 @@ import ( "errors" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) // Hello! This file is auto-generated. diff --git a/internal/settings/types_test.go b/internal/settings/types_test.go index 95398b175..2279fb9ca 100644 --- a/internal/settings/types_test.go +++ b/internal/settings/types_test.go @@ -5,7 +5,7 @@ import ( "github.com/jmoiron/sqlx/types" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) func TestKV_Bool(t *testing.T) { diff --git a/internal/store/store_test.go b/internal/store/store_test.go index a80266f18..9732bb532 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -5,7 +5,7 @@ import ( "io" "testing" - "github.com/crusttech/crust/internal/test" + "github.com/cortezaproject/corteza-server/internal/test" ) func TestStore(t *testing.T) { diff --git a/internal/store/util.go b/internal/store/util.go index 2bb90ed89..e77d926f2 100644 --- a/internal/store/util.go +++ b/internal/store/util.go @@ -2,14 +2,12 @@ package store import ( "io" - "mime/multipart" "net/url" "github.com/pkg/errors" - "github.com/crusttech/crust/internal/config" - "github.com/crusttech/crust/internal/http" + "github.com/cortezaproject/corteza-server/internal/http" ) func FromURL(fileURL string) (io.ReadCloser, error) { @@ -19,9 +17,10 @@ func FromURL(fileURL string) (io.ReadCloser, error) { return nil, errors.New("Only HTTPS is supported for file uploads") } - client, err := http.New(&config.HTTPClient{ + client, err := http.New(&http.Config{ Timeout: 10, }) + if err != nil { return nil, errors.WithStack(err) } diff --git a/internal/subscription/check.go b/internal/subscription/check.go deleted file mode 100644 index 8c13fd2f7..000000000 --- a/internal/subscription/check.go +++ /dev/null @@ -1,97 +0,0 @@ -package subscription - -import ( - "context" - "time" - - "github.com/pkg/errors" - "go.uber.org/zap" - - "github.com/crusttech/permit/pkg/permit" - - "github.com/crusttech/crust/internal/logger" -) - -const ( - // How many times we try to get the licence - checkMaxTries = 10 - - // Number of seconds between each try (x number of tries) - checkTryDelay = 10 - - // Timeout in seconds - checkTimeout = 30 -) - -// Check for subscription -func Check(ctx context.Context) (err error) { - var ( - done context.CancelFunc - p *permit.Permit - try = 1 - in = permit.Permit{ - Key: flags.subscription.Key, - Domain: flags.subscription.Domain, - } - ) - - // Do not collect stats on local domains. - // if p.Domain != "local.crust.tech" { - // @todo collect & pass attributes (no of users....) to be validated by permit.crust.tech subscription server. - in.Attributes = map[string]int{ - "messaging.enabled": 1, - // "messaging.max-public-channels": 1, - // "messaging.max-messages": 1, - // "messaging.max-users": 1, - // "messaging.max-private-channels": 1, - - "system.enabled": 1, - // "system.max-organisations": 1, - // "system.max-users": 1, - // "system.max-teams": 1, - - "compose.enabled": 1, - // "compose.max-modules": 1, - // "compose.max-pages": 1, - // "compose.max-triggers": 1, - // "compose.max-users": 1, - // "compose.max-namespaces": 1, - // "compose.max-charts": 1, - } - // } - - for { - ctx, done = context.WithTimeout(ctx, time.Second*checkTimeout) - defer done() - - p, err = permit.Check(ctx, in) - if err == nil { - break - } - - if err != nil { - logger.Default().Warn( - "unable to check for subscription", - zap.Error(err), - zap.Int("try", try), - zap.Int("max", checkMaxTries), - ) - } - - if try >= checkMaxTries { - return errors.Wrap(err, "unable to check for subscription") - } - - time.Sleep(time.Second * checkTryDelay * checkTryDelay) - - try++ - } - - if !p.IsValid() { - return err - } else if p.Domain != flags.subscription.Domain { - return errors.Errorf("subscription domains do not match (%s <> %s)", p.Domain, flags.subscription.Domain) - } - - return nil -} diff --git a/internal/subscription/flags.go b/internal/subscription/flags.go deleted file mode 100644 index 02e2283fb..000000000 --- a/internal/subscription/flags.go +++ /dev/null @@ -1,35 +0,0 @@ -package subscription - -import ( - "github.com/crusttech/crust/internal/config" -) - -type ( - localFlags struct { - subscription *config.Subscription - } -) - -var flags *localFlags - -// Flags matches signature for main() -func Flags(prefix ...string) { - new(localFlags).Init(prefix...) -} - -func (f *localFlags) Validate() error { - if err := f.subscription.Validate(); err != nil { - return err - } - return nil -} - -func (f *localFlags) Init(prefix ...string) *localFlags { - if flags != nil { - return flags - } - flags = &localFlags{ - new(config.Subscription).Init(prefix...), - } - return flags -} diff --git a/internal/subscription/monitor.go b/internal/subscription/monitor.go deleted file mode 100644 index 49666b52f..000000000 --- a/internal/subscription/monitor.go +++ /dev/null @@ -1,59 +0,0 @@ -package subscription - -import ( - "context" - "os" - "time" - - "go.uber.org/zap" - - "github.com/crusttech/crust/internal/logger" -) - -// Starts subscription checker -func Monitor(ctx context.Context) context.Context { - log := logger.Default() - - check := func(ctx context.Context) bool { - log.Debug("validating subscription") - if err := Check(ctx); err != nil { - log.Error("subscription could not be validated", zap.Error(err)) - return false - } else { - log.Info("subscription validated") - } - - return true - } - - if !check(ctx) { - // Initial subscription check failed, - // Just exit. - os.Exit(1) - } - - // Initialize new context with cancellation we'll return this context and use it from this point on so that we make - // a clean exist in case subscription checking fails - ctx, cancel := context.WithCancel(ctx) - - go func() { - // Validate subscription key every 24h ours (from the last start of the service) - t := time.NewTicker(time.Hour * 24) - defer t.Stop() - - for { - select { - case <-t.C: - // Check the subscription again and call cancel on context - if !check(ctx) { - cancel() - os.Exit(1) - } - case <-ctx.Done(): - return - } - } - }() - - return ctx -} diff --git a/monolith/monolith.go b/monolith/monolith.go new file mode 100644 index 000000000..ceb584162 --- /dev/null +++ b/monolith/monolith.go @@ -0,0 +1,195 @@ +package monolith + +import ( + "context" + + "github.com/go-chi/chi" + _ "github.com/joho/godotenv/autoload" + "github.com/spf13/cobra" + "go.uber.org/zap" + + "github.com/cortezaproject/corteza-server/compose" + "github.com/cortezaproject/corteza-server/internal/logger" + "github.com/cortezaproject/corteza-server/messaging" + "github.com/cortezaproject/corteza-server/pkg/api" + "github.com/cortezaproject/corteza-server/pkg/cli" + "github.com/cortezaproject/corteza-server/pkg/cli/flags" + "github.com/cortezaproject/corteza-server/system" +) + +type ( + // Sets up compose messaging & system subservices and runs them as one. + Monolith struct { + log *zap.Logger + + // General + logOpt *flags.LogOpt + smtpOpt *flags.SMTPOpt + jwtOpt *flags.JWTOpt + httpClientOpt *flags.HttpClientOpt + + compose subservice + messaging subservice + system subservice + } + + subservice interface { + AddCommands(cmd *cobra.Command, ctx context.Context) + BindApiServerFlags(cmd *cobra.Command) + StartServices(ctx context.Context) (err error) + ApiServerPreRun(ctx context.Context) error + ApiServerRoutes(r chi.Router) + ProvisionMigrateDatabase(ctx context.Context) error + ProvisionAccessControl(ctx context.Context) error + } + + runner func(context.Context) error + runners []runner +) + +func (rr runners) run(ctx context.Context) (err error) { + for i := range rr { + err = rr[i](ctx) + if err != nil { + return + } + } + + return +} + +func init() { + logger.Init(zap.DebugLevel) +} + +func InitMonolith() *Monolith { + return &Monolith{ + log: logger.Default(), + compose: compose.InitCompose(), + messaging: messaging.InitMessaging(), + system: system.InitSystem(), + } +} + +// Command produces cobra.Command +func (m *Monolith) Command(ctx context.Context) (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "corteza-server-monolith", + TraverseChildren: true, + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + cli.InitGeneralServices(m.logOpt, m.smtpOpt, m.jwtOpt, m.httpClientOpt) + + return m.StartServices(ctx) + }, + } + + m.BindGlobalFlags(cmd) + + srv := api.NewServer(m.log) + serveApiCmd := srv.Command(ctx, "", m.ApiServerPreRun) + + // Bind all flags we need for serving monolith + m.BindApiServerFlags(serveApiCmd) + + srv.MountRoutes(m.ApiServerRoutes) + + cmd.AddCommand( + serveApiCmd, + cli.SetupProvisionSubcommands(ctx, m), + ) + + m.AddCommands(cmd, ctx) + + return +} + +// AddCommands - other commands that this subservices need +// +// We wrap each seubservice's set into a subcommand so that we do not get naming collisions +func (m *Monolith) AddCommands(cmd *cobra.Command, ctx context.Context) { + var ( + composeCmd = &cobra.Command{Use: "compose"} + messagingCmd = &cobra.Command{Use: "messaging"} + systemCmd = &cobra.Command{Use: "system"} + ) + + m.compose.AddCommands(composeCmd, ctx) + if len(composeCmd.Commands()) > 0 { + cmd.AddCommand(composeCmd) + } + + m.messaging.AddCommands(messagingCmd, ctx) + if len(messagingCmd.Commands()) > 0 { + cmd.AddCommand(messagingCmd) + } + + m.system.AddCommands(systemCmd, ctx) + if len(systemCmd.Commands()) > 0 { + cmd.AddCommand(systemCmd) + } + +} + +// Binds all global flags +func (m *Monolith) BindGlobalFlags(cmd *cobra.Command) { + m.logOpt = flags.Log(cmd) + m.smtpOpt = flags.SMTP(cmd) + m.jwtOpt = flags.JWT(cmd) + m.httpClientOpt = flags.HttpClient(cmd) +} + +// BindApiServerFlags sets & binds all API server flags +func (m *Monolith) BindApiServerFlags(cmd *cobra.Command) { + m.compose.BindApiServerFlags(cmd) + m.messaging.BindApiServerFlags(cmd) + m.system.BindApiServerFlags(cmd) +} + +func (m *Monolith) StartServices(ctx context.Context) (err error) { + return (runners{ + m.compose.StartServices, + m.messaging.StartServices, + m.system.StartServices, + }).run(ctx) +} + +// ApiServerPreRun is executed before serve-api command runs REST API server +// +// Should initialize all that needs to run in the background +func (m Monolith) ApiServerPreRun(ctx context.Context) error { + return (runners{ + m.compose.ApiServerPreRun, + m.messaging.ApiServerPreRun, + m.system.ApiServerPreRun, + }).run(ctx) +} + +// ApiServerRoutes mounts api server routes +func (m *Monolith) ApiServerRoutes(r chi.Router) { + r.Route("/compose", m.compose.ApiServerRoutes) + r.Route("/messaging", m.messaging.ApiServerRoutes) + r.Route("/system", m.system.ApiServerRoutes) +} + +// ProvisionMigrateDatabase migrates database to new version +// +// This is ran by default on serve-api (when not explicitly disabled with --compose-provision-database=false) +// or on demand with "provision migrate-database" +func (m Monolith) ProvisionMigrateDatabase(ctx context.Context) error { + return (runners{ + m.compose.ProvisionMigrateDatabase, + m.messaging.ProvisionMigrateDatabase, + m.system.ProvisionMigrateDatabase, + }).run(ctx) +} + +// ProvisionAccessControl resets access-control rules for roles admin (2) and everyone (1) +// +// Run with emand with "provision access-control-rules" +func (m Monolith) ProvisionAccessControl(ctx context.Context) error { + return (runners{ + m.compose.ProvisionAccessControl, + m.messaging.ProvisionAccessControl, + m.system.ProvisionAccessControl, + }).run(ctx) +} diff --git a/internal/middleware/cors.go b/pkg/api/cors.go similarity index 96% rename from internal/middleware/cors.go rename to pkg/api/cors.go index 238e7a92d..66c184220 100644 --- a/internal/middleware/cors.go +++ b/pkg/api/cors.go @@ -1,4 +1,4 @@ -package middleware +package api import ( "net/http" diff --git a/pkg/api/debug.go b/pkg/api/debug.go new file mode 100644 index 000000000..d1d51f1cb --- /dev/null +++ b/pkg/api/debug.go @@ -0,0 +1,37 @@ +package api + +import ( + "fmt" + "net/http" + "reflect" + "runtime" + + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" +) + +func Debug(r chi.Router) { + r.Mount("/debug", middleware.Profiler()) + DebugRoutes(r) +} + +func DebugRoutes(r chi.Router) { + r.Get("/debug/routes", func(w http.ResponseWriter, req *http.Request) { + var printRoutes func(chi.Routes, string) + + printRoutes = func(r chi.Routes, pfix string) { + routes := r.Routes() + for _, route := range routes { + if route.SubRoutes != nil && len(route.SubRoutes.Routes()) > 0 { + printRoutes(route.SubRoutes, pfix+route.Pattern[:len(route.Pattern)-2]) + } else { + for method, fn := range route.Handlers { + fmt.Fprintf(w, "%-8s %-80s -> %s\n", method, pfix+route.Pattern, runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()) + } + } + } + } + + printRoutes(r, "") + }) +} diff --git a/internal/middleware/logger.go b/pkg/api/logger.go similarity index 83% rename from internal/middleware/logger.go rename to pkg/api/logger.go index d6bbfd681..284050eb3 100644 --- a/internal/middleware/logger.go +++ b/pkg/api/logger.go @@ -1,4 +1,4 @@ -package middleware +package api import ( "net/http" @@ -8,14 +8,14 @@ import ( "github.com/go-chi/chi/middleware" "go.uber.org/zap" - "github.com/crusttech/crust/internal/logger" + "github.com/cortezaproject/corteza-server/internal/logger" ) -// ContextLogger middleware binds logger to request's context. +// contextLogger middleware binds logger to request's context. // // This allows us to use logger from context (with requestID) // inside our (generated) handers and controllers -func ContextLogger(log *zap.Logger) func(next http.Handler) http.Handler { +func contextLogger(log *zap.Logger) func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { var requestID = middleware.GetReqID(req.Context()) @@ -34,7 +34,7 @@ func ContextLogger(log *zap.Logger) func(next http.Handler) http.Handler { // LogRequest middleware logs request details // -// It uses logger from context, see ContextLogger() +// It uses logger from context, see contextLogger() func LogRequest(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { var remote = req.RemoteAddr @@ -55,7 +55,7 @@ func LogRequest(next http.Handler) http.Handler { // LogResponse middleware logs response details // -// It uses logger from context, see ContextLogger() +// It uses logger from context, see contextLogger() func LogResponse(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { wrapped := middleware.NewWrapResponseWriter(w, req.ProtoMajor) @@ -68,7 +68,7 @@ func LogResponse(next http.Handler) http.Handler { zap.String("path", req.URL.Path), zap.Int("status", wrapped.Status()), zap.Int("size", wrapped.BytesWritten()), - zap.Float64("duration", time.Now().Sub(t).Seconds()), + zap.Float64("duration", time.Since(t).Seconds()), ) }() diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go new file mode 100644 index 000000000..7069c0256 --- /dev/null +++ b/pkg/api/metrics.go @@ -0,0 +1,24 @@ +package api + +import ( + "net/http" + + "github.com/766b/chi-prometheus" + "github.com/99designs/basicauth-go" + "github.com/go-chi/chi" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// Middleware is the request logger that provides metrics to prometheus +func Middleware(name string) func(http.Handler) http.Handler { + return chiprometheus.NewMiddleware(name) +} + +func Mount(r chi.Router, username, password string) { + r.Group(func(r chi.Router) { + r.Use(basicauth.New("Metrics", map[string][]string{ + username: {password}, + })) + r.Handle("/metrics", promhttp.Handler()) + }) +} diff --git a/pkg/api/middleware.go b/pkg/api/middleware.go new file mode 100644 index 000000000..6d5054606 --- /dev/null +++ b/pkg/api/middleware.go @@ -0,0 +1,24 @@ +package api + +import ( + "net/http" + + "github.com/go-chi/chi/middleware" + "go.uber.org/zap" +) + +func Base() []func(http.Handler) http.Handler { + return []func(http.Handler) http.Handler{ + handleCORS, + middleware.RealIP, + middleware.RequestID, + } +} + +func Logging(log *zap.Logger) []func(http.Handler) http.Handler { + return []func(http.Handler) http.Handler{ + contextLogger(log), + LogRequest, + LogResponse, + } +} diff --git a/internal/metrics/monitor.go b/pkg/api/monitor.go similarity index 94% rename from internal/metrics/monitor.go rename to pkg/api/monitor.go index c4f4088c2..6c4f7ba23 100644 --- a/internal/metrics/monitor.go +++ b/pkg/api/monitor.go @@ -1,4 +1,4 @@ -package metrics +package api import ( "expvar" @@ -7,7 +7,7 @@ import ( "go.uber.org/zap" - "github.com/crusttech/crust/internal/logger" + "github.com/cortezaproject/corteza-server/internal/logger" ) type Monitor struct { diff --git a/pkg/api/server.go b/pkg/api/server.go new file mode 100644 index 000000000..c88b16714 --- /dev/null +++ b/pkg/api/server.go @@ -0,0 +1,129 @@ +package api + +import ( + "context" + "fmt" + "net" + "net/http" + + "github.com/go-chi/chi" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/titpetric/factory/resputil" + "go.uber.org/zap" + + "github.com/cortezaproject/corteza-server/internal/auth" + "github.com/cortezaproject/corteza-server/internal/version" + "github.com/cortezaproject/corteza-server/pkg/cli/flags" +) + +type ( + Server struct { + name string + + log *zap.Logger + + httpOpt *flags.HTTPOpt + monitorOpt *flags.MonitorOpt + + endpoints []func(r chi.Router) + } +) + +func NewServer(log *zap.Logger) *Server { + return &Server{ + endpoints: make([]func(r chi.Router), 0), + log: log.Named("http"), + } +} + +func (s *Server) Command(ctx context.Context, prefix string, preRun func(context.Context) error) (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "serve-api", + Short: "Start HTTP Server with REST API", + + // Connect all the wires, prepare services, run watchers, bind endpoints + PreRun: func(cmd *cobra.Command, args []string) { + if s.monitorOpt.Interval > 0 { + go NewMonitor(s.monitorOpt.Interval) + } + + preRun(ctx) + }, + + // Run the server + RunE: func(cmd *cobra.Command, args []string) error { + return s.Serve(ctx) + }, + } + + s.BindApiServerFlags(cmd, prefix) + return +} + +func (s *Server) BindApiServerFlags(cmd *cobra.Command, prefix string) { + s.httpOpt = flags.HTTP(cmd, prefix) + s.monitorOpt = flags.Monitor(cmd, prefix) +} + +func (s *Server) MountRoutes(mm ...func(chi.Router)) { + s.endpoints = append(s.endpoints, mm...) +} + +func (s Server) Serve(ctx context.Context) error { + s.log.Info("Starting HTTP server with REST API", zap.String("address", s.httpOpt.Addr)) + + // configure resputil options + resputil.SetConfig(resputil.Options{ + Pretty: s.httpOpt.Pretty, + Trace: s.httpOpt.Tracing, + Logger: func(err error) { + // @todo: error logging + }, + }) + + listener, err := net.Listen("tcp", s.httpOpt.Addr) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Can't listen on addr %s", s.httpOpt.Addr)) + } + + router := chi.NewRouter() + + router.Use(Base()...) + + if s.httpOpt.Logging { + router.Use(Logging(s.log)...) + } + + if s.httpOpt.EnableMetrics { + router.Use(Middleware(s.httpOpt.MetricsServiceLabel)) + } + + router.Group(func(r chi.Router) { + r.Use( + auth.DefaultJwtHandler.Verifier(), + auth.DefaultJwtHandler.Authenticator(), + ) + + for _, mountRoutes := range s.endpoints { + mountRoutes(r) + } + }) + + if s.httpOpt.EnableMetrics { + Mount(router, s.httpOpt.MetricsUsername, s.httpOpt.MetricsPassword) + } + + if s.httpOpt.EnableDebugRoute { + Debug(router) + } + + if s.httpOpt.EnableVersionRoute { + router.Get("/version", version.HttpHandler) + } + + go http.Serve(listener, router) + <-ctx.Done() + + return nil +} diff --git a/pkg/cli/context.go b/pkg/cli/context.go new file mode 100644 index 000000000..70abf89d6 --- /dev/null +++ b/pkg/cli/context.go @@ -0,0 +1,16 @@ +package cli + +import ( + "context" + + ctxwrap "github.com/SentimensRG/ctx" + "github.com/SentimensRG/ctx/sigctx" +) + +// Context is small wrapper that returns sig-term bound context +// +// This can be used as (proper) background context that properly terminates +// all subroutines. +func Context() context.Context { + return ctxwrap.AsContext(sigctx.New()) +} diff --git a/pkg/cli/flags/db.go b/pkg/cli/flags/db.go new file mode 100644 index 000000000..ced9415ce --- /dev/null +++ b/pkg/cli/flags/db.go @@ -0,0 +1,26 @@ +package flags + +import ( + "github.com/spf13/cobra" +) + +type ( + DBOpt struct { + DSN string + Profiler string + } +) + +func DB(cmd *cobra.Command, pfix string) (o *DBOpt) { + o = &DBOpt{} + + bindString(cmd, &o.DSN, + pFlag(pfix, "db-dsn"), "corteza:corteza@tcp(db:3306)/corteza?collation=utf8mb4_general_ci", + "DSN for database connection") + + bindString(cmd, &o.Profiler, + pFlag(pfix, "db-profiler"), "none", + "Profiler for DB queries (none, stdout, logger)") + + return +} diff --git a/pkg/cli/flags/flags.go b/pkg/cli/flags/flags.go new file mode 100644 index 000000000..d140248bc --- /dev/null +++ b/pkg/cli/flags/flags.go @@ -0,0 +1,56 @@ +package flags + +import ( + "os" + "strings" + "time" + + "github.com/spf13/cast" + "github.com/spf13/cobra" +) + +// Prefixes flag +func pFlag(pfix, name string) string { + if pfix != "" { + name = pfix + "-" + name + } + + return name +} + +// Converts input (flag-name) into ENVIRONMENTAL_VARIABLE_KEY +func envKey(s string) string { + return strings.ToUpper(strings.ReplaceAll(s, "-", "_")) +} + +func bindString(cmd *cobra.Command, v *string, flag, def string, desc string) { + if env, has := os.LookupEnv(envKey(flag)); has { + def = cast.ToString(env) + } + + cmd.Flags().StringVar(v, flag, def, desc) +} + +func bindBool(cmd *cobra.Command, v *bool, flag string, def bool, desc string) { + if env, has := os.LookupEnv(envKey(flag)); has { + def = cast.ToBool(env) + } + + cmd.Flags().BoolVar(v, flag, def, desc) +} + +func bindInt(cmd *cobra.Command, v *int, flag string, def int, desc string) { + if env, has := os.LookupEnv(envKey(flag)); has { + def = cast.ToInt(env) + } + + cmd.Flags().IntVar(v, flag, def, desc) +} + +func bindDuration(cmd *cobra.Command, v *time.Duration, flag string, def time.Duration, desc string) { + if env, has := os.LookupEnv(envKey(flag)); has { + def = cast.ToDuration(env) + } + + cmd.Flags().DurationVar(v, flag, def, desc) +} diff --git a/pkg/cli/flags/http.go b/pkg/cli/flags/http.go new file mode 100644 index 000000000..fa9ac457b --- /dev/null +++ b/pkg/cli/flags/http.go @@ -0,0 +1,71 @@ +package flags + +import ( + "github.com/spf13/cobra" + + "github.com/cortezaproject/corteza-server/internal/rand" +) + +type ( + HTTPOpt struct { + Addr string + Logging bool + Pretty bool + Tracing bool + + EnableVersionRoute bool + EnableDebugRoute bool + + EnableMetrics bool + MetricsServiceLabel string + MetricsUsername string + MetricsPassword string + } +) + +func HTTP(cmd *cobra.Command, pfix string) (o *HTTPOpt) { + o = &HTTPOpt{} + + bindString(cmd, &o.Addr, + pFlag(pfix, "http-addr"), ":80", + "Listen address for HTTP server") + + bindBool(cmd, &o.Logging, + pFlag(pfix, "http-log"), true, + "Enable/disable HTTP request log") + + bindBool(cmd, &o.Pretty, + pFlag(pfix, "http-pretty-json"), false, + "Prettify returned JSON output") + + bindBool(cmd, &o.Tracing, + pFlag(pfix, "http-error-tracing"), false, + "Return error stack frame") + + bindBool(cmd, &o.EnableVersionRoute, + pFlag(pfix, "http-enable-version-route"), true, + "Enable /version route") + + bindBool(cmd, &o.EnableDebugRoute, + pFlag(pfix, "http-enable-debug-route"), false, + "Enable /debug route with pprof data") + + bindBool(cmd, &o.EnableMetrics, + pFlag(pfix, "http-metrics"), false, + "Enable metrics") + + bindString(cmd, &o.MetricsServiceLabel, + pFlag(pfix, "http-metrics-name"), "corteza", + "Provide metrics service label for Prometheus") + + bindString(cmd, &o.MetricsUsername, + pFlag(pfix, "http-metrics-username"), "metrics", + "Provide metrics username for Prometheus") + + // Setting metrics password to random string to prevent security accidents... + bindString(cmd, &o.MetricsPassword, + pFlag(pfix, "http-metrics-password"), string(rand.Bytes(5)), + "Provide metrics password for Prometheus") + + return +} diff --git a/pkg/cli/flags/http_client.go b/pkg/cli/flags/http_client.go new file mode 100644 index 000000000..68ca750d1 --- /dev/null +++ b/pkg/cli/flags/http_client.go @@ -0,0 +1,28 @@ +package flags + +import ( + "time" + + "github.com/spf13/cobra" +) + +type ( + HttpClientOpt struct { + ClientTSLInsecure bool + HttpClientTimeout time.Duration + } +) + +func HttpClient(cmd *cobra.Command) (o *HttpClientOpt) { + o = &HttpClientOpt{} + + bindBool(cmd, &o.ClientTSLInsecure, + "http-client-tsl-insecure", false, + "Skip insecure TSL verification on outbound HTTP requests (allow invalid/self-signed certificates") + + bindDuration(cmd, &o.HttpClientTimeout, + "http-client-timeout", 30*time.Second, + "Default HTTP client timeout") + + return +} diff --git a/pkg/cli/flags/jwt.go b/pkg/cli/flags/jwt.go new file mode 100644 index 000000000..6734a91b0 --- /dev/null +++ b/pkg/cli/flags/jwt.go @@ -0,0 +1,29 @@ +package flags + +import ( + "github.com/spf13/cobra" + + "github.com/cortezaproject/corteza-server/internal/rand" +) + +type ( + JWTOpt struct { + Secret string + Expiry int + } +) + +func JWT(cmd *cobra.Command) (o *JWTOpt) { + o = &JWTOpt{} + + // Setting JWT secret to random string to prevent security accidents... + bindString(cmd, &o.Secret, + "auth-jwt-secret", string(rand.Bytes(32)), + "JWT Secret") + + bindInt(cmd, &o.Expiry, + "auth-jwt-expiry", 60*24*30, + "JWT Expiration in minutes") + + return +} diff --git a/pkg/cli/flags/log.go b/pkg/cli/flags/log.go new file mode 100644 index 000000000..c833d27f6 --- /dev/null +++ b/pkg/cli/flags/log.go @@ -0,0 +1,26 @@ +package flags + +import ( + "github.com/spf13/cobra" +) + +type ( + LogOpt struct { + Level string + JSON bool + } +) + +func Log(cmd *cobra.Command) (o *LogOpt) { + o = &LogOpt{} + + bindString(cmd, &o.Level, + "log-level", "info", + "Log level (debug, info, warn, error, panic, fatal)") + + bindBool(cmd, &o.JSON, + "log-json", true, + "Log in JSON format") + + return +} diff --git a/pkg/cli/flags/monitor.go b/pkg/cli/flags/monitor.go new file mode 100644 index 000000000..b7bf47d09 --- /dev/null +++ b/pkg/cli/flags/monitor.go @@ -0,0 +1,21 @@ +package flags + +import ( + "github.com/spf13/cobra" +) + +type ( + MonitorOpt struct { + Interval int + } +) + +func Monitor(cmd *cobra.Command, pfix string) (o *MonitorOpt) { + o = &MonitorOpt{} + + bindInt(cmd, &o.Interval, + pFlag(pfix, "monitor-interval"), 300, + "Monitor interval (seconds, 0 = disable)") + + return +} diff --git a/pkg/cli/flags/provision.go b/pkg/cli/flags/provision.go new file mode 100644 index 000000000..f9319769f --- /dev/null +++ b/pkg/cli/flags/provision.go @@ -0,0 +1,21 @@ +package flags + +import ( + "github.com/spf13/cobra" +) + +type ( + ProvisionOpt struct { + Database bool + } +) + +func Provision(cmd *cobra.Command, pfix string) (o *ProvisionOpt) { + o = &ProvisionOpt{} + + bindBool(cmd, &o.Database, + pFlag(pfix, "provision-database"), true, + "Run database migration scripts") + + return +} diff --git a/pkg/cli/flags/pubsub.go b/pkg/cli/flags/pubsub.go new file mode 100644 index 000000000..28ce9b661 --- /dev/null +++ b/pkg/cli/flags/pubsub.go @@ -0,0 +1,58 @@ +package flags + +import ( + "time" + + "github.com/spf13/cobra" +) + +type ( + PubSubOpt struct { + Mode string + + // Mode + PollingInterval time.Duration + + // Redis + RedisAddr string + RedisTimeout time.Duration + RedisPingTimeout time.Duration + RedisPingPeriod time.Duration + } +) + +func PubSub(cmd *cobra.Command, pfix string) (o *PubSubOpt) { + o = &PubSubOpt{} + + const ( + timeout = 15 * time.Second + pingTimeout = 120 * time.Second + pingPeriod = (pingTimeout * 9) / 10 + ) + + bindString(cmd, &o.Mode, + pFlag(pfix, "pubsub-mode"), "poll", + "Pub/Sub mode (poll, redis") + + bindDuration(cmd, &o.RedisPingTimeout, + pFlag(pfix, "pubsub-polling-interval"), timeout, + "Sub/Sub polling interval") + + bindString(cmd, &o.RedisAddr, + pFlag(pfix, "pubsub-redis-addr"), "redis:6379", + "Pub/Sub mode (poll, redis") + + bindDuration(cmd, &o.RedisTimeout, + pFlag(pfix, "pubsub-redis-timeout"), timeout, + "Websocket connection timeout") + + bindDuration(cmd, &o.RedisPingTimeout, + pFlag(pfix, "pubsub-redis-ping-timeout"), pingTimeout, + "Pub/Sub connection ping timeout") + + bindDuration(cmd, &o.RedisPingPeriod, + pFlag(pfix, "pubsub-redis-ping-period"), pingPeriod, + "Pub/Sub connection ping period (should be lower than timeout)") + + return +} diff --git a/pkg/cli/flags/smtp.go b/pkg/cli/flags/smtp.go new file mode 100644 index 000000000..ee4ea2e63 --- /dev/null +++ b/pkg/cli/flags/smtp.go @@ -0,0 +1,41 @@ +package flags + +import ( + "github.com/spf13/cobra" +) + +type ( + SMTPOpt struct { + Host string + Port int + User string + Pass string + From string + } +) + +func SMTP(cmd *cobra.Command) (o *SMTPOpt) { + o = &SMTPOpt{} + + bindString(cmd, &o.Host, + "smtp-host", "localhost:25", + "SMTP hostname") + + bindString(cmd, &o.User, + "smtp-username", "", + "SMTP server username") + + bindString(cmd, &o.Pass, + "smtp-pass", "", + "SMTP server password") + + bindString(cmd, &o.From, + "smtp-from", "", + "Sender's email address") + + bindInt(cmd, &o.Port, + "smtp-port", 25, + "SMTP port number") + + return +} diff --git a/pkg/cli/flags/websocket.go b/pkg/cli/flags/websocket.go new file mode 100644 index 000000000..5b9cf9ee3 --- /dev/null +++ b/pkg/cli/flags/websocket.go @@ -0,0 +1,39 @@ +package flags + +import ( + "time" + + "github.com/spf13/cobra" +) + +type ( + WebsocketOpt struct { + Timeout time.Duration + PingTimeout time.Duration + PingPeriod time.Duration + } +) + +func Websocket(cmd *cobra.Command, pfix string) (o *WebsocketOpt) { + o = &WebsocketOpt{} + + const ( + timeout = 15 * time.Second + pingTimeout = 120 * time.Second + pingPeriod = (pingTimeout * 9) / 10 + ) + + bindDuration(cmd, &o.Timeout, + pFlag(pfix, "websocket-timeout"), timeout, + "Websocket connection timeout") + + bindDuration(cmd, &o.PingTimeout, + pFlag(pfix, "websocket-ping-timeout"), pingTimeout, + "Websocket connection ping timeout") + + bindDuration(cmd, &o.PingPeriod, + pFlag(pfix, "websocket-ping-period"), pingPeriod, + "Websocket connection ping period (should be lower than timeout)") + + return +} diff --git a/pkg/cli/helpers.go b/pkg/cli/helpers.go new file mode 100644 index 000000000..c38cbaaac --- /dev/null +++ b/pkg/cli/helpers.go @@ -0,0 +1,107 @@ +package cli + +import ( + "context" + + "github.com/spf13/cobra" + "go.uber.org/zap" + + "github.com/cortezaproject/corteza-server/internal/auth" + "github.com/cortezaproject/corteza-server/internal/http" + "github.com/cortezaproject/corteza-server/internal/logger" + "github.com/cortezaproject/corteza-server/internal/mail" + "github.com/cortezaproject/corteza-server/pkg/cli/flags" +) + +// SetupProvisionCommands sets-up standard provision commands +// Deprecated: use SetupProvisionSubCommands +func SetupProvisionCommands(ac func() error, md func() error) *cobra.Command { + var ( + cmd = &cobra.Command{ + Use: "provision", + Short: "Provision tasks", + } + ) + + // Add only commands with defined callbacks + if ac != nil { + cmd.AddCommand(&cobra.Command{ + Use: "access-control-rules", + Short: "Reset access control rules & roles", + + RunE: func(cmd *cobra.Command, args []string) error { + return ac() + }, + }) + } + + // Add only commands with defined callbacks + if md != nil { + cmd.AddCommand(&cobra.Command{ + Use: "migrate-database", + Short: "Run database migration scripts", + + RunE: func(cmd *cobra.Command, args []string) error { + return md() + }, + }) + } + + return cmd +} + +type ( + provisioner interface { + ProvisionMigrateDatabase(ctx context.Context) error + ProvisionAccessControl(ctx context.Context) error + } +) + +func SetupProvisionSubcommands(ctx context.Context, p provisioner) *cobra.Command { + var ( + cmd = &cobra.Command{ + Use: "provision", + Short: "Provision tasks", + } + ) + + // Add only commands with defined callbacks + cmd.AddCommand(&cobra.Command{ + Use: "access-control-rules", + Short: "Reset access control rules & roles", + + RunE: func(cmd *cobra.Command, args []string) error { + return p.ProvisionAccessControl(ctx) + }, + }) + + // Add only commands with defined callbacks + cmd.AddCommand(&cobra.Command{ + Use: "migrate-database", + Short: "Run database migration scripts", + + RunE: func(cmd *cobra.Command, args []string) error { + return p.ProvisionMigrateDatabase(ctx) + }, + }) + + return cmd +} + +func InitGeneralServices(logOpt *flags.LogOpt, smtpOpt *flags.SMTPOpt, jwtOpt *flags.JWTOpt, httpClientOpt *flags.HttpClientOpt) { + var logLevel = zap.InfoLevel + _ = logLevel.Set(logOpt.Level) + + if logger.Default() == nil { + logger.Init(logLevel) + } else { + logger.DefaultLevel.SetLevel(logLevel) + } + + auth.SetupDefault(jwtOpt.Secret, jwtOpt.Expiry) + mail.SetupDialer(smtpOpt.Host, smtpOpt.Port, smtpOpt.User, smtpOpt.Pass, smtpOpt.From) + http.SetupDefaults( + httpClientOpt.HttpClientTimeout, + httpClientOpt.ClientTSLInsecure, + ) +} diff --git a/vendor/github.com/crusttech/permit/LICENCE b/vendor/github.com/crusttech/permit/LICENCE deleted file mode 100644 index d64569567..000000000 --- a/vendor/github.com/crusttech/permit/LICENCE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/crusttech/permit/pkg/permit/client.go b/vendor/github.com/crusttech/permit/pkg/permit/client.go deleted file mode 100644 index 20eda3db5..000000000 --- a/vendor/github.com/crusttech/permit/pkg/permit/client.go +++ /dev/null @@ -1,70 +0,0 @@ -package permit - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - - "github.com/pkg/errors" -) - -type ( - httpClient interface { - Do(req *http.Request) (*http.Response, error) - } -) - -const ( - permitCheckEndpoint = "https://permit.crust.tech/check" -) - -func Check(ctx context.Context, p Permit) (*Permit, error) { - return CheckWithClient(ctx, http.DefaultClient, p) -} - -func CheckWithClient(ctx context.Context, client httpClient, p Permit) (*Permit, error) { - if len(p.Key) == 0 { - return nil, errors.New("key not set") - } else if len(p.Key) != KeyLength { - return nil, errors.Errorf("invalid key length (%d chars)", len(p.Key)) - } - - buf := &bytes.Buffer{} - if err := json.NewEncoder(buf).Encode(p); err != nil { - return nil, errors.Wrap(err, "permit encoding failed") - } - - if req, err := http.NewRequest("POST", permitCheckEndpoint, buf); err != nil { - return nil, errors.Wrap(err, "unable to create request") - } else { - return CheckWithRequest(client, req.WithContext(ctx)) - } -} - -func CheckWithRequest(client httpClient, request *http.Request) (p *Permit, err error) { - var rsp *http.Response - if rsp, err = client.Do(request); err != nil { - return nil, errors.Wrap(err, "unable to fetch permit") - } - - defer rsp.Body.Close() - - switch rsp.StatusCode { - case http.StatusBadRequest: - return nil, errors.New("bad request") - case http.StatusNotFound: - return nil, errors.New("subscription key not found") - case http.StatusInternalServerError: - return nil, errors.New("subscription server error") - case http.StatusUnauthorized: - return nil, errors.New("subscription key invalid") - } - - p = &Permit{} - if err = json.NewDecoder(rsp.Body).Decode(&p); err != nil { - return nil, errors.Wrap(err, "unable to decode response into permit") - } - - return -} diff --git a/vendor/github.com/crusttech/permit/pkg/permit/permit.go b/vendor/github.com/crusttech/permit/pkg/permit/permit.go deleted file mode 100644 index e193213b7..000000000 --- a/vendor/github.com/crusttech/permit/pkg/permit/permit.go +++ /dev/null @@ -1,35 +0,0 @@ -package permit - -import ( - "time" - - "github.com/pkg/errors" -) - -type ( - Permit struct { - Version uint `json:"version"` - Key string `json:"key"` - Domain string `json:"domain"` - Expires *time.Time `json:"expires,omitempty"` - Valid bool `json:"valid"` - Attributes map[string]int `json:"attributes"` - } -) - -const ( - // KeyLength - KeyLength = 64 -) - -var ( - PermitNotFound = errors.New("permit not found") -) - -func (p Permit) IsValid() bool { - return p.Valid && !p.Expired() -} - -func (p Permit) Expired() bool { - return p.Expires != nil && p.Expires.Before(time.Now()) -} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go new file mode 100644 index 000000000..67b56d37c --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go @@ -0,0 +1,199 @@ +// 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 promhttp + +import ( + "bufio" + "io" + "net" + "net/http" +) + +const ( + closeNotifier = 1 << iota + flusher + hijacker + readerFrom + pusher +) + +type delegator interface { + http.ResponseWriter + + Status() int + Written() int64 +} + +type responseWriterDelegator struct { + http.ResponseWriter + + handler, method string + status int + written int64 + wroteHeader bool + observeWriteHeader func(int) +} + +func (r *responseWriterDelegator) Status() int { + return r.status +} + +func (r *responseWriterDelegator) Written() int64 { + return r.written +} + +func (r *responseWriterDelegator) WriteHeader(code int) { + r.status = code + r.wroteHeader = true + r.ResponseWriter.WriteHeader(code) + if r.observeWriteHeader != nil { + r.observeWriteHeader(code) + } +} + +func (r *responseWriterDelegator) Write(b []byte) (int, error) { + if !r.wroteHeader { + r.WriteHeader(http.StatusOK) + } + n, err := r.ResponseWriter.Write(b) + r.written += int64(n) + return n, err +} + +type closeNotifierDelegator struct{ *responseWriterDelegator } +type flusherDelegator struct{ *responseWriterDelegator } +type hijackerDelegator struct{ *responseWriterDelegator } +type readerFromDelegator struct{ *responseWriterDelegator } + +func (d closeNotifierDelegator) CloseNotify() <-chan bool { + return d.ResponseWriter.(http.CloseNotifier).CloseNotify() +} +func (d flusherDelegator) Flush() { + d.ResponseWriter.(http.Flusher).Flush() +} +func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return d.ResponseWriter.(http.Hijacker).Hijack() +} +func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) { + if !d.wroteHeader { + d.WriteHeader(http.StatusOK) + } + n, err := d.ResponseWriter.(io.ReaderFrom).ReadFrom(re) + d.written += n + return n, err +} + +var pickDelegator = make([]func(*responseWriterDelegator) delegator, 32) + +func init() { + // TODO(beorn7): Code generation would help here. + pickDelegator[0] = func(d *responseWriterDelegator) delegator { // 0 + return d + } + pickDelegator[closeNotifier] = func(d *responseWriterDelegator) delegator { // 1 + return closeNotifierDelegator{d} + } + pickDelegator[flusher] = func(d *responseWriterDelegator) delegator { // 2 + return flusherDelegator{d} + } + pickDelegator[flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 3 + return struct { + *responseWriterDelegator + http.Flusher + http.CloseNotifier + }{d, flusherDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[hijacker] = func(d *responseWriterDelegator) delegator { // 4 + return hijackerDelegator{d} + } + pickDelegator[hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 5 + return struct { + *responseWriterDelegator + http.Hijacker + http.CloseNotifier + }{d, hijackerDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 6 + return struct { + *responseWriterDelegator + http.Hijacker + http.Flusher + }{d, hijackerDelegator{d}, flusherDelegator{d}} + } + pickDelegator[hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 7 + return struct { + *responseWriterDelegator + http.Hijacker + http.Flusher + http.CloseNotifier + }{d, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[readerFrom] = func(d *responseWriterDelegator) delegator { // 8 + return readerFromDelegator{d} + } + pickDelegator[readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 9 + return struct { + *responseWriterDelegator + io.ReaderFrom + http.CloseNotifier + }{d, readerFromDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 10 + return struct { + *responseWriterDelegator + io.ReaderFrom + http.Flusher + }{d, readerFromDelegator{d}, flusherDelegator{d}} + } + pickDelegator[readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 11 + return struct { + *responseWriterDelegator + io.ReaderFrom + http.Flusher + http.CloseNotifier + }{d, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 12 + return struct { + *responseWriterDelegator + io.ReaderFrom + http.Hijacker + }{d, readerFromDelegator{d}, hijackerDelegator{d}} + } + pickDelegator[readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 13 + return struct { + *responseWriterDelegator + io.ReaderFrom + http.Hijacker + http.CloseNotifier + }{d, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 14 + return struct { + *responseWriterDelegator + io.ReaderFrom + http.Hijacker + http.Flusher + }{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}} + } + pickDelegator[readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 15 + return struct { + *responseWriterDelegator + io.ReaderFrom + http.Hijacker + http.Flusher + http.CloseNotifier + }{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} + } +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go new file mode 100644 index 000000000..31a706956 --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go @@ -0,0 +1,181 @@ +// 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. + +// +build go1.8 + +package promhttp + +import ( + "io" + "net/http" +) + +type pusherDelegator struct{ *responseWriterDelegator } + +func (d pusherDelegator) Push(target string, opts *http.PushOptions) error { + return d.ResponseWriter.(http.Pusher).Push(target, opts) +} + +func init() { + pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16 + return pusherDelegator{d} + } + pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17 + return struct { + *responseWriterDelegator + http.Pusher + http.CloseNotifier + }{d, pusherDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18 + return struct { + *responseWriterDelegator + http.Pusher + http.Flusher + }{d, pusherDelegator{d}, flusherDelegator{d}} + } + pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19 + return struct { + *responseWriterDelegator + http.Pusher + http.Flusher + http.CloseNotifier + }{d, pusherDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20 + return struct { + *responseWriterDelegator + http.Pusher + http.Hijacker + }{d, pusherDelegator{d}, hijackerDelegator{d}} + } + pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21 + return struct { + *responseWriterDelegator + http.Pusher + http.Hijacker + http.CloseNotifier + }{d, pusherDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22 + return struct { + *responseWriterDelegator + http.Pusher + http.Hijacker + http.Flusher + }{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}} + } + pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23 + return struct { + *responseWriterDelegator + http.Pusher + http.Hijacker + http.Flusher + http.CloseNotifier + }{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24 + return struct { + *responseWriterDelegator + http.Pusher + io.ReaderFrom + }{d, pusherDelegator{d}, readerFromDelegator{d}} + } + pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25 + return struct { + *responseWriterDelegator + http.Pusher + io.ReaderFrom + http.CloseNotifier + }{d, pusherDelegator{d}, readerFromDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26 + return struct { + *responseWriterDelegator + http.Pusher + io.ReaderFrom + http.Flusher + }{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}} + } + pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27 + return struct { + *responseWriterDelegator + http.Pusher + io.ReaderFrom + http.Flusher + http.CloseNotifier + }{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28 + return struct { + *responseWriterDelegator + http.Pusher + io.ReaderFrom + http.Hijacker + }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}} + } + pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29 + return struct { + *responseWriterDelegator + http.Pusher + io.ReaderFrom + http.Hijacker + http.CloseNotifier + }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}} + } + pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30 + return struct { + *responseWriterDelegator + http.Pusher + io.ReaderFrom + http.Hijacker + http.Flusher + }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}} + } + pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31 + return struct { + *responseWriterDelegator + http.Pusher + io.ReaderFrom + http.Hijacker + http.Flusher + http.CloseNotifier + }{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}} + } +} + +func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator { + d := &responseWriterDelegator{ + ResponseWriter: w, + observeWriteHeader: observeWriteHeaderFunc, + } + + id := 0 + if _, ok := w.(http.CloseNotifier); ok { + id += closeNotifier + } + if _, ok := w.(http.Flusher); ok { + id += flusher + } + if _, ok := w.(http.Hijacker); ok { + id += hijacker + } + if _, ok := w.(io.ReaderFrom); ok { + id += readerFrom + } + if _, ok := w.(http.Pusher); ok { + id += pusher + } + + return pickDelegator[id](d) +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go new file mode 100644 index 000000000..8bb9b8b68 --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go @@ -0,0 +1,44 @@ +// 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. + +// +build !go1.8 + +package promhttp + +import ( + "io" + "net/http" +) + +func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator { + d := &responseWriterDelegator{ + ResponseWriter: w, + observeWriteHeader: observeWriteHeaderFunc, + } + + id := 0 + if _, ok := w.(http.CloseNotifier); ok { + id += closeNotifier + } + if _, ok := w.(http.Flusher); ok { + id += flusher + } + if _, ok := w.(http.Hijacker); ok { + id += hijacker + } + if _, ok := w.(io.ReaderFrom); ok { + id += readerFrom + } + + return pickDelegator[id](d) +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go new file mode 100644 index 000000000..668eb6b3c --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go @@ -0,0 +1,311 @@ +// Copyright 2016 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 promhttp provides tooling around HTTP servers and clients. +// +// First, the package allows the creation of http.Handler instances to expose +// Prometheus metrics via HTTP. promhttp.Handler acts on the +// prometheus.DefaultGatherer. With HandlerFor, you can create a handler for a +// custom registry or anything that implements the Gatherer interface. It also +// allows the creation of handlers that act differently on errors or allow to +// log errors. +// +// Second, the package provides tooling to instrument instances of http.Handler +// via middleware. Middleware wrappers follow the naming scheme +// InstrumentHandlerX, where X describes the intended use of the middleware. +// See each function's doc comment for specific details. +// +// Finally, the package allows for an http.RoundTripper to be instrumented via +// middleware. Middleware wrappers follow the naming scheme +// InstrumentRoundTripperX, where X describes the intended use of the +// middleware. See each function's doc comment for specific details. +package promhttp + +import ( + "compress/gzip" + "fmt" + "io" + "net/http" + "strings" + "sync" + "time" + + "github.com/prometheus/common/expfmt" + + "github.com/prometheus/client_golang/prometheus" +) + +const ( + contentTypeHeader = "Content-Type" + contentLengthHeader = "Content-Length" + contentEncodingHeader = "Content-Encoding" + acceptEncodingHeader = "Accept-Encoding" +) + +var gzipPool = sync.Pool{ + New: func() interface{} { + return gzip.NewWriter(nil) + }, +} + +// Handler returns an http.Handler for the prometheus.DefaultGatherer, using +// default HandlerOpts, i.e. it reports the first error as an HTTP error, it has +// no error logging, and it applies compression if requested by the client. +// +// The returned http.Handler is already instrumented using the +// InstrumentMetricHandler function and the prometheus.DefaultRegisterer. If you +// create multiple http.Handlers by separate calls of the Handler function, the +// metrics used for instrumentation will be shared between them, providing +// global scrape counts. +// +// This function is meant to cover the bulk of basic use cases. If you are doing +// anything that requires more customization (including using a non-default +// Gatherer, different instrumentation, and non-default HandlerOpts), use the +// HandlerFor function. See there for details. +func Handler() http.Handler { + return InstrumentMetricHandler( + prometheus.DefaultRegisterer, HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}), + ) +} + +// HandlerFor returns an uninstrumented http.Handler for the provided +// Gatherer. The behavior of the Handler is defined by the provided +// HandlerOpts. Thus, HandlerFor is useful to create http.Handlers for custom +// Gatherers, with non-default HandlerOpts, and/or with custom (or no) +// instrumentation. Use the InstrumentMetricHandler function to apply the same +// kind of instrumentation as it is used by the Handler function. +func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { + var inFlightSem chan struct{} + if opts.MaxRequestsInFlight > 0 { + inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight) + } + + h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) { + if inFlightSem != nil { + select { + case inFlightSem <- struct{}{}: // All good, carry on. + defer func() { <-inFlightSem }() + default: + http.Error(rsp, fmt.Sprintf( + "Limit of concurrent requests reached (%d), try again later.", opts.MaxRequestsInFlight, + ), http.StatusServiceUnavailable) + return + } + } + mfs, err := reg.Gather() + if err != nil { + if opts.ErrorLog != nil { + opts.ErrorLog.Println("error gathering metrics:", err) + } + switch opts.ErrorHandling { + case PanicOnError: + panic(err) + case ContinueOnError: + if len(mfs) == 0 { + // Still report the error if no metrics have been gathered. + httpError(rsp, err) + return + } + case HTTPErrorOnError: + httpError(rsp, err) + return + } + } + + contentType := expfmt.Negotiate(req.Header) + header := rsp.Header() + header.Set(contentTypeHeader, string(contentType)) + + w := io.Writer(rsp) + if !opts.DisableCompression && gzipAccepted(req.Header) { + header.Set(contentEncodingHeader, "gzip") + gz := gzipPool.Get().(*gzip.Writer) + defer gzipPool.Put(gz) + + gz.Reset(w) + defer gz.Close() + + w = gz + } + + enc := expfmt.NewEncoder(w, contentType) + + var lastErr error + for _, mf := range mfs { + if err := enc.Encode(mf); err != nil { + lastErr = err + if opts.ErrorLog != nil { + opts.ErrorLog.Println("error encoding and sending metric family:", err) + } + switch opts.ErrorHandling { + case PanicOnError: + panic(err) + case ContinueOnError: + // Handled later. + case HTTPErrorOnError: + httpError(rsp, err) + return + } + } + } + + if lastErr != nil { + httpError(rsp, lastErr) + } + }) + + if opts.Timeout <= 0 { + return h + } + return http.TimeoutHandler(h, opts.Timeout, fmt.Sprintf( + "Exceeded configured timeout of %v.\n", + opts.Timeout, + )) +} + +// InstrumentMetricHandler is usually used with an http.Handler returned by the +// HandlerFor function. It instruments the provided http.Handler with two +// metrics: A counter vector "promhttp_metric_handler_requests_total" to count +// scrapes partitioned by HTTP status code, and a gauge +// "promhttp_metric_handler_requests_in_flight" to track the number of +// simultaneous scrapes. This function idempotently registers collectors for +// both metrics with the provided Registerer. It panics if the registration +// fails. The provided metrics are useful to see how many scrapes hit the +// monitored target (which could be from different Prometheus servers or other +// scrapers), and how often they overlap (which would result in more than one +// scrape in flight at the same time). Note that the scrapes-in-flight gauge +// will contain the scrape by which it is exposed, while the scrape counter will +// only get incremented after the scrape is complete (as only then the status +// code is known). For tracking scrape durations, use the +// "scrape_duration_seconds" gauge created by the Prometheus server upon each +// scrape. +func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) http.Handler { + cnt := prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "promhttp_metric_handler_requests_total", + Help: "Total number of scrapes by HTTP status code.", + }, + []string{"code"}, + ) + // Initialize the most likely HTTP status codes. + cnt.WithLabelValues("200") + cnt.WithLabelValues("500") + cnt.WithLabelValues("503") + if err := reg.Register(cnt); err != nil { + if are, ok := err.(prometheus.AlreadyRegisteredError); ok { + cnt = are.ExistingCollector.(*prometheus.CounterVec) + } else { + panic(err) + } + } + + gge := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "promhttp_metric_handler_requests_in_flight", + Help: "Current number of scrapes being served.", + }) + if err := reg.Register(gge); err != nil { + if are, ok := err.(prometheus.AlreadyRegisteredError); ok { + gge = are.ExistingCollector.(prometheus.Gauge) + } else { + panic(err) + } + } + + return InstrumentHandlerCounter(cnt, InstrumentHandlerInFlight(gge, handler)) +} + +// HandlerErrorHandling defines how a Handler serving metrics will handle +// errors. +type HandlerErrorHandling int + +// These constants cause handlers serving metrics to behave as described if +// errors are encountered. +const ( + // Serve an HTTP status code 500 upon the first error + // encountered. Report the error message in the body. + HTTPErrorOnError HandlerErrorHandling = iota + // Ignore errors and try to serve as many metrics as possible. However, + // if no metrics can be served, serve an HTTP status code 500 and the + // last error message in the body. Only use this in deliberate "best + // effort" metrics collection scenarios. It is recommended to at least + // log errors (by providing an ErrorLog in HandlerOpts) to not mask + // errors completely. + ContinueOnError + // Panic upon the first error encountered (useful for "crash only" apps). + PanicOnError +) + +// Logger is the minimal interface HandlerOpts needs for logging. Note that +// log.Logger from the standard library implements this interface, and it is +// easy to implement by custom loggers, if they don't do so already anyway. +type Logger interface { + Println(v ...interface{}) +} + +// HandlerOpts specifies options how to serve metrics via an http.Handler. The +// zero value of HandlerOpts is a reasonable default. +type HandlerOpts struct { + // ErrorLog specifies an optional logger for errors collecting and + // serving metrics. If nil, errors are not logged at all. + ErrorLog Logger + // ErrorHandling defines how errors are handled. Note that errors are + // logged regardless of the configured ErrorHandling provided ErrorLog + // is not nil. + ErrorHandling HandlerErrorHandling + // If DisableCompression is true, the handler will never compress the + // response, even if requested by the client. + DisableCompression bool + // The number of concurrent HTTP requests is limited to + // MaxRequestsInFlight. Additional requests are responded to with 503 + // Service Unavailable and a suitable message in the body. If + // MaxRequestsInFlight is 0 or negative, no limit is applied. + MaxRequestsInFlight int + // If handling a request takes longer than Timeout, it is responded to + // with 503 ServiceUnavailable and a suitable Message. No timeout is + // applied if Timeout is 0 or negative. Note that with the current + // implementation, reaching the timeout simply ends the HTTP requests as + // described above (and even that only if sending of the body hasn't + // started yet), while the bulk work of gathering all the metrics keeps + // running in the background (with the eventual result to be thrown + // away). Until the implementation is improved, it is recommended to + // implement a separate timeout in potentially slow Collectors. + Timeout time.Duration +} + +// gzipAccepted returns whether the client will accept gzip-encoded content. +func gzipAccepted(header http.Header) bool { + a := header.Get(acceptEncodingHeader) + parts := strings.Split(a, ",") + for _, part := range parts { + part = strings.TrimSpace(part) + if part == "gzip" || strings.HasPrefix(part, "gzip;") { + return true + } + } + return false +} + +// httpError removes any content-encoding header and then calls http.Error with +// the provided error and http.StatusInternalServerErrer. Error contents is +// supposed to be uncompressed plain text. However, same as with a plain +// http.Error, any header settings will be void if the header has already been +// sent. The error message will still be written to the writer, but it will +// probably be of limited use. +func httpError(rsp http.ResponseWriter, err error) { + rsp.Header().Del(contentEncodingHeader) + http.Error( + rsp, + "An error has occurred while serving metrics:\n\n"+err.Error(), + http.StatusInternalServerError, + ) +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go new file mode 100644 index 000000000..86fd56447 --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go @@ -0,0 +1,97 @@ +// 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 promhttp + +import ( + "net/http" + "time" + + "github.com/prometheus/client_golang/prometheus" +) + +// The RoundTripperFunc type is an adapter to allow the use of ordinary +// functions as RoundTrippers. If f is a function with the appropriate +// signature, RountTripperFunc(f) is a RoundTripper that calls f. +type RoundTripperFunc func(req *http.Request) (*http.Response, error) + +// RoundTrip implements the RoundTripper interface. +func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) { + return rt(r) +} + +// InstrumentRoundTripperInFlight is a middleware that wraps the provided +// http.RoundTripper. It sets the provided prometheus.Gauge to the number of +// requests currently handled by the wrapped http.RoundTripper. +// +// See the example for ExampleInstrumentRoundTripperDuration for example usage. +func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc { + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + gauge.Inc() + defer gauge.Dec() + return next.RoundTrip(r) + }) +} + +// InstrumentRoundTripperCounter is a middleware that wraps the provided +// http.RoundTripper to observe the request result with the provided CounterVec. +// The CounterVec must have zero, one, or two non-const non-curried labels. For +// those, the only allowed label names are "code" and "method". The function +// panics otherwise. Partitioning of the CounterVec happens by HTTP status code +// and/or HTTP method if the respective instance label names are present in the +// CounterVec. For unpartitioned counting, use a CounterVec with zero labels. +// +// If the wrapped RoundTripper panics or returns a non-nil error, the Counter +// is not incremented. +// +// See the example for ExampleInstrumentRoundTripperDuration for example usage. +func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc { + code, method := checkLabels(counter) + + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + resp, err := next.RoundTrip(r) + if err == nil { + counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc() + } + return resp, err + }) +} + +// InstrumentRoundTripperDuration is a middleware that wraps the provided +// http.RoundTripper to observe the request duration with the provided +// ObserverVec. The ObserverVec must have zero, one, or two non-const +// non-curried labels. For those, the only allowed label names are "code" and +// "method". The function panics otherwise. The Observe method of the Observer +// in the ObserverVec is called with the request duration in +// seconds. Partitioning happens by HTTP status code and/or HTTP method if the +// respective instance label names are present in the ObserverVec. For +// unpartitioned observations, use an ObserverVec with zero labels. Note that +// partitioning of Histograms is expensive and should be used judiciously. +// +// If the wrapped RoundTripper panics or returns a non-nil error, no values are +// reported. +// +// Note that this method is only guaranteed to never observe negative durations +// if used with Go1.9+. +func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc { + code, method := checkLabels(obs) + + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + start := time.Now() + resp, err := next.RoundTrip(r) + if err == nil { + obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds()) + } + return resp, err + }) +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go new file mode 100644 index 000000000..a034d1ec0 --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go @@ -0,0 +1,144 @@ +// 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. + +// +build go1.8 + +package promhttp + +import ( + "context" + "crypto/tls" + "net/http" + "net/http/httptrace" + "time" +) + +// InstrumentTrace is used to offer flexibility in instrumenting the available +// httptrace.ClientTrace hook functions. Each function is passed a float64 +// representing the time in seconds since the start of the http request. A user +// may choose to use separately buckets Histograms, or implement custom +// instance labels on a per function basis. +type InstrumentTrace struct { + GotConn func(float64) + PutIdleConn func(float64) + GotFirstResponseByte func(float64) + Got100Continue func(float64) + DNSStart func(float64) + DNSDone func(float64) + ConnectStart func(float64) + ConnectDone func(float64) + TLSHandshakeStart func(float64) + TLSHandshakeDone func(float64) + WroteHeaders func(float64) + Wait100Continue func(float64) + WroteRequest func(float64) +} + +// InstrumentRoundTripperTrace is a middleware that wraps the provided +// RoundTripper and reports times to hook functions provided in the +// InstrumentTrace struct. Hook functions that are not present in the provided +// InstrumentTrace struct are ignored. Times reported to the hook functions are +// time since the start of the request. Only with Go1.9+, those times are +// guaranteed to never be negative. (Earlier Go versions are not using a +// monotonic clock.) Note that partitioning of Histograms is expensive and +// should be used judiciously. +// +// For hook functions that receive an error as an argument, no observations are +// made in the event of a non-nil error value. +// +// See the example for ExampleInstrumentRoundTripperDuration for example usage. +func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc { + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + start := time.Now() + + trace := &httptrace.ClientTrace{ + GotConn: func(_ httptrace.GotConnInfo) { + if it.GotConn != nil { + it.GotConn(time.Since(start).Seconds()) + } + }, + PutIdleConn: func(err error) { + if err != nil { + return + } + if it.PutIdleConn != nil { + it.PutIdleConn(time.Since(start).Seconds()) + } + }, + DNSStart: func(_ httptrace.DNSStartInfo) { + if it.DNSStart != nil { + it.DNSStart(time.Since(start).Seconds()) + } + }, + DNSDone: func(_ httptrace.DNSDoneInfo) { + if it.DNSDone != nil { + it.DNSDone(time.Since(start).Seconds()) + } + }, + ConnectStart: func(_, _ string) { + if it.ConnectStart != nil { + it.ConnectStart(time.Since(start).Seconds()) + } + }, + ConnectDone: func(_, _ string, err error) { + if err != nil { + return + } + if it.ConnectDone != nil { + it.ConnectDone(time.Since(start).Seconds()) + } + }, + GotFirstResponseByte: func() { + if it.GotFirstResponseByte != nil { + it.GotFirstResponseByte(time.Since(start).Seconds()) + } + }, + Got100Continue: func() { + if it.Got100Continue != nil { + it.Got100Continue(time.Since(start).Seconds()) + } + }, + TLSHandshakeStart: func() { + if it.TLSHandshakeStart != nil { + it.TLSHandshakeStart(time.Since(start).Seconds()) + } + }, + TLSHandshakeDone: func(_ tls.ConnectionState, err error) { + if err != nil { + return + } + if it.TLSHandshakeDone != nil { + it.TLSHandshakeDone(time.Since(start).Seconds()) + } + }, + WroteHeaders: func() { + if it.WroteHeaders != nil { + it.WroteHeaders(time.Since(start).Seconds()) + } + }, + Wait100Continue: func() { + if it.Wait100Continue != nil { + it.Wait100Continue(time.Since(start).Seconds()) + } + }, + WroteRequest: func(_ httptrace.WroteRequestInfo) { + if it.WroteRequest != nil { + it.WroteRequest(time.Since(start).Seconds()) + } + }, + } + r = r.WithContext(httptrace.WithClientTrace(context.Background(), trace)) + + return next.RoundTrip(r) + }) +} diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go new file mode 100644 index 000000000..9db243805 --- /dev/null +++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go @@ -0,0 +1,447 @@ +// 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 promhttp + +import ( + "errors" + "net/http" + "strconv" + "strings" + "time" + + dto "github.com/prometheus/client_model/go" + + "github.com/prometheus/client_golang/prometheus" +) + +// magicString is used for the hacky label test in checkLabels. Remove once fixed. +const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa" + +// InstrumentHandlerInFlight is a middleware that wraps the provided +// http.Handler. It sets the provided prometheus.Gauge to the number of +// requests currently handled by the wrapped http.Handler. +// +// See the example for InstrumentHandlerDuration for example usage. +func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + g.Inc() + defer g.Dec() + next.ServeHTTP(w, r) + }) +} + +// InstrumentHandlerDuration is a middleware that wraps the provided +// http.Handler to observe the request duration with the provided ObserverVec. +// The ObserverVec must have zero, one, or two non-const non-curried labels. For +// those, the only allowed label names are "code" and "method". The function +// panics otherwise. The Observe method of the Observer in the ObserverVec is +// called with the request duration in seconds. Partitioning happens by HTTP +// status code and/or HTTP method if the respective instance label names are +// present in the ObserverVec. For unpartitioned observations, use an +// ObserverVec with zero labels. Note that partitioning of Histograms is +// expensive and should be used judiciously. +// +// If the wrapped Handler does not set a status code, a status code of 200 is assumed. +// +// If the wrapped Handler panics, no values are reported. +// +// Note that this method is only guaranteed to never observe negative durations +// if used with Go1.9+. +func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc { + code, method := checkLabels(obs) + + if code { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + now := time.Now() + d := newDelegator(w, nil) + next.ServeHTTP(d, r) + + obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds()) + }) + } + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + now := time.Now() + next.ServeHTTP(w, r) + obs.With(labels(code, method, r.Method, 0)).Observe(time.Since(now).Seconds()) + }) +} + +// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler +// to observe the request result with the provided CounterVec. The CounterVec +// must have zero, one, or two non-const non-curried labels. For those, the only +// allowed label names are "code" and "method". The function panics +// otherwise. Partitioning of the CounterVec happens by HTTP status code and/or +// HTTP method if the respective instance label names are present in the +// CounterVec. For unpartitioned counting, use a CounterVec with zero labels. +// +// If the wrapped Handler does not set a status code, a status code of 200 is assumed. +// +// If the wrapped Handler panics, the Counter is not incremented. +// +// See the example for InstrumentHandlerDuration for example usage. +func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc { + code, method := checkLabels(counter) + + if code { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + d := newDelegator(w, nil) + next.ServeHTTP(d, r) + counter.With(labels(code, method, r.Method, d.Status())).Inc() + }) + } + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) + counter.With(labels(code, method, r.Method, 0)).Inc() + }) +} + +// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided +// http.Handler to observe with the provided ObserverVec the request duration +// until the response headers are written. The ObserverVec must have zero, one, +// or two non-const non-curried labels. For those, the only allowed label names +// are "code" and "method". The function panics otherwise. The Observe method of +// the Observer in the ObserverVec is called with the request duration in +// seconds. Partitioning happens by HTTP status code and/or HTTP method if the +// respective instance label names are present in the ObserverVec. For +// unpartitioned observations, use an ObserverVec with zero labels. Note that +// partitioning of Histograms is expensive and should be used judiciously. +// +// If the wrapped Handler panics before calling WriteHeader, no value is +// reported. +// +// Note that this method is only guaranteed to never observe negative durations +// if used with Go1.9+. +// +// See the example for InstrumentHandlerDuration for example usage. +func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc { + code, method := checkLabels(obs) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + now := time.Now() + d := newDelegator(w, func(status int) { + obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds()) + }) + next.ServeHTTP(d, r) + }) +} + +// InstrumentHandlerRequestSize is a middleware that wraps the provided +// http.Handler to observe the request size with the provided ObserverVec. The +// ObserverVec must have zero, one, or two non-const non-curried labels. For +// those, the only allowed label names are "code" and "method". The function +// panics otherwise. The Observe method of the Observer in the ObserverVec is +// called with the request size in bytes. Partitioning happens by HTTP status +// code and/or HTTP method if the respective instance label names are present in +// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero +// labels. Note that partitioning of Histograms is expensive and should be used +// judiciously. +// +// If the wrapped Handler does not set a status code, a status code of 200 is assumed. +// +// If the wrapped Handler panics, no values are reported. +// +// See the example for InstrumentHandlerDuration for example usage. +func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc { + code, method := checkLabels(obs) + + if code { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + d := newDelegator(w, nil) + next.ServeHTTP(d, r) + size := computeApproximateRequestSize(r) + obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size)) + }) + } + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) + size := computeApproximateRequestSize(r) + obs.With(labels(code, method, r.Method, 0)).Observe(float64(size)) + }) +} + +// InstrumentHandlerResponseSize is a middleware that wraps the provided +// http.Handler to observe the response size with the provided ObserverVec. The +// ObserverVec must have zero, one, or two non-const non-curried labels. For +// those, the only allowed label names are "code" and "method". The function +// panics otherwise. The Observe method of the Observer in the ObserverVec is +// called with the response size in bytes. Partitioning happens by HTTP status +// code and/or HTTP method if the respective instance label names are present in +// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero +// labels. Note that partitioning of Histograms is expensive and should be used +// judiciously. +// +// If the wrapped Handler does not set a status code, a status code of 200 is assumed. +// +// If the wrapped Handler panics, no values are reported. +// +// See the example for InstrumentHandlerDuration for example usage. +func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler { + code, method := checkLabels(obs) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + d := newDelegator(w, nil) + next.ServeHTTP(d, r) + obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written())) + }) +} + +func checkLabels(c prometheus.Collector) (code bool, method bool) { + // TODO(beorn7): Remove this hacky way to check for instance labels + // once Descriptors can have their dimensionality queried. + var ( + desc *prometheus.Desc + m prometheus.Metric + pm dto.Metric + lvs []string + ) + + // Get the Desc from the Collector. + descc := make(chan *prometheus.Desc, 1) + c.Describe(descc) + + select { + case desc = <-descc: + default: + panic("no description provided by collector") + } + select { + case <-descc: + panic("more than one description provided by collector") + default: + } + + close(descc) + + // Create a ConstMetric with the Desc. Since we don't know how many + // variable labels there are, try for as long as it needs. + for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) { + m, err = prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, lvs...) + } + + // Write out the metric into a proto message and look at the labels. + // If the value is not the magicString, it is a constLabel, which doesn't interest us. + // If the label is curried, it doesn't interest us. + // In all other cases, only "code" or "method" is allowed. + if err := m.Write(&pm); err != nil { + panic("error checking metric for labels") + } + for _, label := range pm.Label { + name, value := label.GetName(), label.GetValue() + if value != magicString || isLabelCurried(c, name) { + continue + } + switch name { + case "code": + code = true + case "method": + method = true + default: + panic("metric partitioned with non-supported labels") + } + } + return +} + +func isLabelCurried(c prometheus.Collector, label string) bool { + // This is even hackier than the label test above. + // We essentially try to curry again and see if it works. + // But for that, we need to type-convert to the two + // types we use here, ObserverVec or *CounterVec. + switch v := c.(type) { + case *prometheus.CounterVec: + if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil { + return false + } + case prometheus.ObserverVec: + if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil { + return false + } + default: + panic("unsupported metric vec type") + } + return true +} + +// emptyLabels is a one-time allocation for non-partitioned metrics to avoid +// unnecessary allocations on each request. +var emptyLabels = prometheus.Labels{} + +func labels(code, method bool, reqMethod string, status int) prometheus.Labels { + if !(code || method) { + return emptyLabels + } + labels := prometheus.Labels{} + + if code { + labels["code"] = sanitizeCode(status) + } + if method { + labels["method"] = sanitizeMethod(reqMethod) + } + + return labels +} + +func computeApproximateRequestSize(r *http.Request) int { + s := 0 + if r.URL != nil { + s += len(r.URL.String()) + } + + s += len(r.Method) + s += len(r.Proto) + for name, values := range r.Header { + s += len(name) + for _, value := range values { + s += len(value) + } + } + s += len(r.Host) + + // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL. + + if r.ContentLength != -1 { + s += int(r.ContentLength) + } + return s +} + +func sanitizeMethod(m string) string { + switch m { + case "GET", "get": + return "get" + case "PUT", "put": + return "put" + case "HEAD", "head": + return "head" + case "POST", "post": + return "post" + case "DELETE", "delete": + return "delete" + case "CONNECT", "connect": + return "connect" + case "OPTIONS", "options": + return "options" + case "NOTIFY", "notify": + return "notify" + default: + return strings.ToLower(m) + } +} + +// If the wrapped http.Handler has not set a status code, i.e. the value is +// currently 0, santizeCode will return 200, for consistency with behavior in +// the stdlib. +func sanitizeCode(s int) string { + switch s { + case 100: + return "100" + case 101: + return "101" + + case 200, 0: + return "200" + case 201: + return "201" + case 202: + return "202" + case 203: + return "203" + case 204: + return "204" + case 205: + return "205" + case 206: + return "206" + + case 300: + return "300" + case 301: + return "301" + case 302: + return "302" + case 304: + return "304" + case 305: + return "305" + case 307: + return "307" + + case 400: + return "400" + case 401: + return "401" + case 402: + return "402" + case 403: + return "403" + case 404: + return "404" + case 405: + return "405" + case 406: + return "406" + case 407: + return "407" + case 408: + return "408" + case 409: + return "409" + case 410: + return "410" + case 411: + return "411" + case 412: + return "412" + case 413: + return "413" + case 414: + return "414" + case 415: + return "415" + case 416: + return "416" + case 417: + return "417" + case 418: + return "418" + + case 500: + return "500" + case 501: + return "501" + case 502: + return "502" + case 503: + return "503" + case 504: + return "504" + case 505: + return "505" + + case 428: + return "428" + case 429: + return "429" + case 431: + return "431" + case 511: + return "511" + + default: + return strconv.Itoa(s) + } +} diff --git a/vendor/github.com/spf13/cast/.gitignore b/vendor/github.com/spf13/cast/.gitignore new file mode 100644 index 000000000..53053a8ac --- /dev/null +++ b/vendor/github.com/spf13/cast/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test + +*.bench diff --git a/vendor/github.com/spf13/cast/.travis.yml b/vendor/github.com/spf13/cast/.travis.yml new file mode 100644 index 000000000..6420d1c27 --- /dev/null +++ b/vendor/github.com/spf13/cast/.travis.yml @@ -0,0 +1,15 @@ +language: go +env: + - GO111MODULE=on +sudo: required +go: + - "1.11.x" + - tip +os: + - linux +matrix: + allow_failures: + - go: tip + fast_finish: true +script: + - make check diff --git a/vendor/github.com/spf13/cast/LICENSE b/vendor/github.com/spf13/cast/LICENSE new file mode 100644 index 000000000..4527efb9c --- /dev/null +++ b/vendor/github.com/spf13/cast/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Steve Francia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/spf13/cast/Makefile b/vendor/github.com/spf13/cast/Makefile new file mode 100644 index 000000000..7ccf8930b --- /dev/null +++ b/vendor/github.com/spf13/cast/Makefile @@ -0,0 +1,38 @@ +# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html + +.PHONY: check fmt lint test test-race vet test-cover-html help +.DEFAULT_GOAL := help + +check: test-race fmt vet lint ## Run tests and linters + +test: ## Run tests + go test ./... + +test-race: ## Run tests with race detector + go test -race ./... + +fmt: ## Run gofmt linter + @for d in `go list` ; do \ + if [ "`gofmt -l -s $$GOPATH/src/$$d | tee /dev/stderr`" ]; then \ + echo "^ improperly formatted go files" && echo && exit 1; \ + fi \ + done + +lint: ## Run golint linter + @for d in `go list` ; do \ + if [ "`golint $$d | tee /dev/stderr`" ]; then \ + echo "^ golint errors!" && echo && exit 1; \ + fi \ + done + +vet: ## Run go vet linter + @if [ "`go vet | tee /dev/stderr`" ]; then \ + echo "^ go vet errors!" && echo && exit 1; \ + fi + +test-cover-html: ## Generate test coverage report + go test -coverprofile=coverage.out -covermode=count + go tool cover -func=coverage.out + +help: + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/vendor/github.com/spf13/cast/README.md b/vendor/github.com/spf13/cast/README.md new file mode 100644 index 000000000..e6939397d --- /dev/null +++ b/vendor/github.com/spf13/cast/README.md @@ -0,0 +1,75 @@ +cast +==== +[![GoDoc](https://godoc.org/github.com/spf13/cast?status.svg)](https://godoc.org/github.com/spf13/cast) +[![Build Status](https://api.travis-ci.org/spf13/cast.svg?branch=master)](https://travis-ci.org/spf13/cast) +[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cast)](https://goreportcard.com/report/github.com/spf13/cast) + +Easy and safe casting from one type to another in Go + +Don’t Panic! ... Cast + +## What is Cast? + +Cast is a library to convert between different go types in a consistent and easy way. + +Cast provides simple functions to easily convert a number to a string, an +interface into a bool, etc. Cast does this intelligently when an obvious +conversion is possible. It doesn’t make any attempts to guess what you meant, +for example you can only convert a string to an int when it is a string +representation of an int such as “8”. Cast was developed for use in +[Hugo](http://hugo.spf13.com), a website engine which uses YAML, TOML or JSON +for meta data. + +## Why use Cast? + +When working with dynamic data in Go you often need to cast or convert the data +from one type into another. Cast goes beyond just using type assertion (though +it uses that when possible) to provide a very straightforward and convenient +library. + +If you are working with interfaces to handle things like dynamic content +you’ll need an easy way to convert an interface into a given type. This +is the library for you. + +If you are taking in data from YAML, TOML or JSON or other formats which lack +full types, then Cast is the library for you. + +## Usage + +Cast provides a handful of To_____ methods. These methods will always return +the desired type. **If input is provided that will not convert to that type, the +0 or nil value for that type will be returned**. + +Cast also provides identical methods To_____E. These return the same result as +the To_____ methods, plus an additional error which tells you if it successfully +converted. Using these methods you can tell the difference between when the +input matched the zero value or when the conversion failed and the zero value +was returned. + +The following examples are merely a sample of what is available. Please review +the code for a complete set. + +### Example ‘ToString’: + + cast.ToString("mayonegg") // "mayonegg" + cast.ToString(8) // "8" + cast.ToString(8.31) // "8.31" + cast.ToString([]byte("one time")) // "one time" + cast.ToString(nil) // "" + + var foo interface{} = "one more time" + cast.ToString(foo) // "one more time" + + +### Example ‘ToInt’: + + cast.ToInt(8) // 8 + cast.ToInt(8.31) // 8 + cast.ToInt("8") // 8 + cast.ToInt(true) // 1 + cast.ToInt(false) // 0 + + var eight interface{} = 8 + cast.ToInt(eight) // 8 + cast.ToInt(nil) // 0 + diff --git a/vendor/github.com/spf13/cast/cast.go b/vendor/github.com/spf13/cast/cast.go new file mode 100644 index 000000000..9fba638d4 --- /dev/null +++ b/vendor/github.com/spf13/cast/cast.go @@ -0,0 +1,171 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Package cast provides easy and safe casting in Go. +package cast + +import "time" + +// ToBool casts an interface to a bool type. +func ToBool(i interface{}) bool { + v, _ := ToBoolE(i) + return v +} + +// ToTime casts an interface to a time.Time type. +func ToTime(i interface{}) time.Time { + v, _ := ToTimeE(i) + return v +} + +// ToDuration casts an interface to a time.Duration type. +func ToDuration(i interface{}) time.Duration { + v, _ := ToDurationE(i) + return v +} + +// ToFloat64 casts an interface to a float64 type. +func ToFloat64(i interface{}) float64 { + v, _ := ToFloat64E(i) + return v +} + +// ToFloat32 casts an interface to a float32 type. +func ToFloat32(i interface{}) float32 { + v, _ := ToFloat32E(i) + return v +} + +// ToInt64 casts an interface to an int64 type. +func ToInt64(i interface{}) int64 { + v, _ := ToInt64E(i) + return v +} + +// ToInt32 casts an interface to an int32 type. +func ToInt32(i interface{}) int32 { + v, _ := ToInt32E(i) + return v +} + +// ToInt16 casts an interface to an int16 type. +func ToInt16(i interface{}) int16 { + v, _ := ToInt16E(i) + return v +} + +// ToInt8 casts an interface to an int8 type. +func ToInt8(i interface{}) int8 { + v, _ := ToInt8E(i) + return v +} + +// ToInt casts an interface to an int type. +func ToInt(i interface{}) int { + v, _ := ToIntE(i) + return v +} + +// ToUint casts an interface to a uint type. +func ToUint(i interface{}) uint { + v, _ := ToUintE(i) + return v +} + +// ToUint64 casts an interface to a uint64 type. +func ToUint64(i interface{}) uint64 { + v, _ := ToUint64E(i) + return v +} + +// ToUint32 casts an interface to a uint32 type. +func ToUint32(i interface{}) uint32 { + v, _ := ToUint32E(i) + return v +} + +// ToUint16 casts an interface to a uint16 type. +func ToUint16(i interface{}) uint16 { + v, _ := ToUint16E(i) + return v +} + +// ToUint8 casts an interface to a uint8 type. +func ToUint8(i interface{}) uint8 { + v, _ := ToUint8E(i) + return v +} + +// ToString casts an interface to a string type. +func ToString(i interface{}) string { + v, _ := ToStringE(i) + return v +} + +// ToStringMapString casts an interface to a map[string]string type. +func ToStringMapString(i interface{}) map[string]string { + v, _ := ToStringMapStringE(i) + return v +} + +// ToStringMapStringSlice casts an interface to a map[string][]string type. +func ToStringMapStringSlice(i interface{}) map[string][]string { + v, _ := ToStringMapStringSliceE(i) + return v +} + +// ToStringMapBool casts an interface to a map[string]bool type. +func ToStringMapBool(i interface{}) map[string]bool { + v, _ := ToStringMapBoolE(i) + return v +} + +// ToStringMapInt casts an interface to a map[string]int type. +func ToStringMapInt(i interface{}) map[string]int { + v, _ := ToStringMapIntE(i) + return v +} + +// ToStringMapInt64 casts an interface to a map[string]int64 type. +func ToStringMapInt64(i interface{}) map[string]int64 { + v, _ := ToStringMapInt64E(i) + return v +} + +// ToStringMap casts an interface to a map[string]interface{} type. +func ToStringMap(i interface{}) map[string]interface{} { + v, _ := ToStringMapE(i) + return v +} + +// ToSlice casts an interface to a []interface{} type. +func ToSlice(i interface{}) []interface{} { + v, _ := ToSliceE(i) + return v +} + +// ToBoolSlice casts an interface to a []bool type. +func ToBoolSlice(i interface{}) []bool { + v, _ := ToBoolSliceE(i) + return v +} + +// ToStringSlice casts an interface to a []string type. +func ToStringSlice(i interface{}) []string { + v, _ := ToStringSliceE(i) + return v +} + +// ToIntSlice casts an interface to a []int type. +func ToIntSlice(i interface{}) []int { + v, _ := ToIntSliceE(i) + return v +} + +// ToDurationSlice casts an interface to a []time.Duration type. +func ToDurationSlice(i interface{}) []time.Duration { + v, _ := ToDurationSliceE(i) + return v +} diff --git a/vendor/github.com/spf13/cast/caste.go b/vendor/github.com/spf13/cast/caste.go new file mode 100644 index 000000000..a4859fb0a --- /dev/null +++ b/vendor/github.com/spf13/cast/caste.go @@ -0,0 +1,1249 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast + +import ( + "encoding/json" + "errors" + "fmt" + "html/template" + "reflect" + "strconv" + "strings" + "time" +) + +var errNegativeNotAllowed = errors.New("unable to cast negative value") + +// ToTimeE casts an interface to a time.Time type. +func ToTimeE(i interface{}) (tim time.Time, err error) { + i = indirect(i) + + switch v := i.(type) { + case time.Time: + return v, nil + case string: + return StringToDate(v) + case int: + return time.Unix(int64(v), 0), nil + case int64: + return time.Unix(v, 0), nil + case int32: + return time.Unix(int64(v), 0), nil + case uint: + return time.Unix(int64(v), 0), nil + case uint64: + return time.Unix(int64(v), 0), nil + case uint32: + return time.Unix(int64(v), 0), nil + default: + return time.Time{}, fmt.Errorf("unable to cast %#v of type %T to Time", i, i) + } +} + +// ToDurationE casts an interface to a time.Duration type. +func ToDurationE(i interface{}) (d time.Duration, err error) { + i = indirect(i) + + switch s := i.(type) { + case time.Duration: + return s, nil + case int, int64, int32, int16, int8, uint, uint64, uint32, uint16, uint8: + d = time.Duration(ToInt64(s)) + return + case float32, float64: + d = time.Duration(ToFloat64(s)) + return + case string: + if strings.ContainsAny(s, "nsuµmh") { + d, err = time.ParseDuration(s) + } else { + d, err = time.ParseDuration(s + "ns") + } + return + default: + err = fmt.Errorf("unable to cast %#v of type %T to Duration", i, i) + return + } +} + +// ToBoolE casts an interface to a bool type. +func ToBoolE(i interface{}) (bool, error) { + i = indirect(i) + + switch b := i.(type) { + case bool: + return b, nil + case nil: + return false, nil + case int: + if i.(int) != 0 { + return true, nil + } + return false, nil + case string: + return strconv.ParseBool(i.(string)) + default: + return false, fmt.Errorf("unable to cast %#v of type %T to bool", i, i) + } +} + +// ToFloat64E casts an interface to a float64 type. +func ToFloat64E(i interface{}) (float64, error) { + i = indirect(i) + + switch s := i.(type) { + case float64: + return s, nil + case float32: + return float64(s), nil + case int: + return float64(s), nil + case int64: + return float64(s), nil + case int32: + return float64(s), nil + case int16: + return float64(s), nil + case int8: + return float64(s), nil + case uint: + return float64(s), nil + case uint64: + return float64(s), nil + case uint32: + return float64(s), nil + case uint16: + return float64(s), nil + case uint8: + return float64(s), nil + case string: + v, err := strconv.ParseFloat(s, 64) + if err == nil { + return v, nil + } + return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i) + case bool: + if s { + return 1, nil + } + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to float64", i, i) + } +} + +// ToFloat32E casts an interface to a float32 type. +func ToFloat32E(i interface{}) (float32, error) { + i = indirect(i) + + switch s := i.(type) { + case float64: + return float32(s), nil + case float32: + return s, nil + case int: + return float32(s), nil + case int64: + return float32(s), nil + case int32: + return float32(s), nil + case int16: + return float32(s), nil + case int8: + return float32(s), nil + case uint: + return float32(s), nil + case uint64: + return float32(s), nil + case uint32: + return float32(s), nil + case uint16: + return float32(s), nil + case uint8: + return float32(s), nil + case string: + v, err := strconv.ParseFloat(s, 32) + if err == nil { + return float32(v), nil + } + return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i) + case bool: + if s { + return 1, nil + } + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to float32", i, i) + } +} + +// ToInt64E casts an interface to an int64 type. +func ToInt64E(i interface{}) (int64, error) { + i = indirect(i) + + switch s := i.(type) { + case int: + return int64(s), nil + case int64: + return s, nil + case int32: + return int64(s), nil + case int16: + return int64(s), nil + case int8: + return int64(s), nil + case uint: + return int64(s), nil + case uint64: + return int64(s), nil + case uint32: + return int64(s), nil + case uint16: + return int64(s), nil + case uint8: + return int64(s), nil + case float64: + return int64(s), nil + case float32: + return int64(s), nil + case string: + v, err := strconv.ParseInt(s, 0, 0) + if err == nil { + return v, nil + } + return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i) + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to int64", i, i) + } +} + +// ToInt32E casts an interface to an int32 type. +func ToInt32E(i interface{}) (int32, error) { + i = indirect(i) + + switch s := i.(type) { + case int: + return int32(s), nil + case int64: + return int32(s), nil + case int32: + return s, nil + case int16: + return int32(s), nil + case int8: + return int32(s), nil + case uint: + return int32(s), nil + case uint64: + return int32(s), nil + case uint32: + return int32(s), nil + case uint16: + return int32(s), nil + case uint8: + return int32(s), nil + case float64: + return int32(s), nil + case float32: + return int32(s), nil + case string: + v, err := strconv.ParseInt(s, 0, 0) + if err == nil { + return int32(v), nil + } + return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i) + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to int32", i, i) + } +} + +// ToInt16E casts an interface to an int16 type. +func ToInt16E(i interface{}) (int16, error) { + i = indirect(i) + + switch s := i.(type) { + case int: + return int16(s), nil + case int64: + return int16(s), nil + case int32: + return int16(s), nil + case int16: + return s, nil + case int8: + return int16(s), nil + case uint: + return int16(s), nil + case uint64: + return int16(s), nil + case uint32: + return int16(s), nil + case uint16: + return int16(s), nil + case uint8: + return int16(s), nil + case float64: + return int16(s), nil + case float32: + return int16(s), nil + case string: + v, err := strconv.ParseInt(s, 0, 0) + if err == nil { + return int16(v), nil + } + return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i) + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to int16", i, i) + } +} + +// ToInt8E casts an interface to an int8 type. +func ToInt8E(i interface{}) (int8, error) { + i = indirect(i) + + switch s := i.(type) { + case int: + return int8(s), nil + case int64: + return int8(s), nil + case int32: + return int8(s), nil + case int16: + return int8(s), nil + case int8: + return s, nil + case uint: + return int8(s), nil + case uint64: + return int8(s), nil + case uint32: + return int8(s), nil + case uint16: + return int8(s), nil + case uint8: + return int8(s), nil + case float64: + return int8(s), nil + case float32: + return int8(s), nil + case string: + v, err := strconv.ParseInt(s, 0, 0) + if err == nil { + return int8(v), nil + } + return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i) + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to int8", i, i) + } +} + +// ToIntE casts an interface to an int type. +func ToIntE(i interface{}) (int, error) { + i = indirect(i) + + switch s := i.(type) { + case int: + return s, nil + case int64: + return int(s), nil + case int32: + return int(s), nil + case int16: + return int(s), nil + case int8: + return int(s), nil + case uint: + return int(s), nil + case uint64: + return int(s), nil + case uint32: + return int(s), nil + case uint16: + return int(s), nil + case uint8: + return int(s), nil + case float64: + return int(s), nil + case float32: + return int(s), nil + case string: + v, err := strconv.ParseInt(s, 0, 0) + if err == nil { + return int(v), nil + } + return 0, fmt.Errorf("unable to cast %#v of type %T to int", i, i) + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to int", i, i) + } +} + +// ToUintE casts an interface to a uint type. +func ToUintE(i interface{}) (uint, error) { + i = indirect(i) + + switch s := i.(type) { + case string: + v, err := strconv.ParseUint(s, 0, 0) + if err == nil { + return uint(v), nil + } + return 0, fmt.Errorf("unable to cast %#v to uint: %s", i, err) + case int: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint(s), nil + case int64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint(s), nil + case int32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint(s), nil + case int16: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint(s), nil + case int8: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint(s), nil + case uint: + return s, nil + case uint64: + return uint(s), nil + case uint32: + return uint(s), nil + case uint16: + return uint(s), nil + case uint8: + return uint(s), nil + case float64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint(s), nil + case float32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint(s), nil + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to uint", i, i) + } +} + +// ToUint64E casts an interface to a uint64 type. +func ToUint64E(i interface{}) (uint64, error) { + i = indirect(i) + + switch s := i.(type) { + case string: + v, err := strconv.ParseUint(s, 0, 64) + if err == nil { + return v, nil + } + return 0, fmt.Errorf("unable to cast %#v to uint64: %s", i, err) + case int: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint64(s), nil + case int64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint64(s), nil + case int32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint64(s), nil + case int16: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint64(s), nil + case int8: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint64(s), nil + case uint: + return uint64(s), nil + case uint64: + return s, nil + case uint32: + return uint64(s), nil + case uint16: + return uint64(s), nil + case uint8: + return uint64(s), nil + case float32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint64(s), nil + case float64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint64(s), nil + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to uint64", i, i) + } +} + +// ToUint32E casts an interface to a uint32 type. +func ToUint32E(i interface{}) (uint32, error) { + i = indirect(i) + + switch s := i.(type) { + case string: + v, err := strconv.ParseUint(s, 0, 32) + if err == nil { + return uint32(v), nil + } + return 0, fmt.Errorf("unable to cast %#v to uint32: %s", i, err) + case int: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint32(s), nil + case int64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint32(s), nil + case int32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint32(s), nil + case int16: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint32(s), nil + case int8: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint32(s), nil + case uint: + return uint32(s), nil + case uint64: + return uint32(s), nil + case uint32: + return s, nil + case uint16: + return uint32(s), nil + case uint8: + return uint32(s), nil + case float64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint32(s), nil + case float32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint32(s), nil + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to uint32", i, i) + } +} + +// ToUint16E casts an interface to a uint16 type. +func ToUint16E(i interface{}) (uint16, error) { + i = indirect(i) + + switch s := i.(type) { + case string: + v, err := strconv.ParseUint(s, 0, 16) + if err == nil { + return uint16(v), nil + } + return 0, fmt.Errorf("unable to cast %#v to uint16: %s", i, err) + case int: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint16(s), nil + case int64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint16(s), nil + case int32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint16(s), nil + case int16: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint16(s), nil + case int8: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint16(s), nil + case uint: + return uint16(s), nil + case uint64: + return uint16(s), nil + case uint32: + return uint16(s), nil + case uint16: + return s, nil + case uint8: + return uint16(s), nil + case float64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint16(s), nil + case float32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint16(s), nil + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to uint16", i, i) + } +} + +// ToUint8E casts an interface to a uint type. +func ToUint8E(i interface{}) (uint8, error) { + i = indirect(i) + + switch s := i.(type) { + case string: + v, err := strconv.ParseUint(s, 0, 8) + if err == nil { + return uint8(v), nil + } + return 0, fmt.Errorf("unable to cast %#v to uint8: %s", i, err) + case int: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint8(s), nil + case int64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint8(s), nil + case int32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint8(s), nil + case int16: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint8(s), nil + case int8: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint8(s), nil + case uint: + return uint8(s), nil + case uint64: + return uint8(s), nil + case uint32: + return uint8(s), nil + case uint16: + return uint8(s), nil + case uint8: + return s, nil + case float64: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint8(s), nil + case float32: + if s < 0 { + return 0, errNegativeNotAllowed + } + return uint8(s), nil + case bool: + if s { + return 1, nil + } + return 0, nil + case nil: + return 0, nil + default: + return 0, fmt.Errorf("unable to cast %#v of type %T to uint8", i, i) + } +} + +// From html/template/content.go +// Copyright 2011 The Go Authors. All rights reserved. +// indirect returns the value, after dereferencing as many times +// as necessary to reach the base type (or nil). +func indirect(a interface{}) interface{} { + if a == nil { + return nil + } + if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr { + // Avoid creating a reflect.Value if it's not a pointer. + return a + } + v := reflect.ValueOf(a) + for v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + return v.Interface() +} + +// From html/template/content.go +// Copyright 2011 The Go Authors. All rights reserved. +// indirectToStringerOrError returns the value, after dereferencing as many times +// as necessary to reach the base type (or nil) or an implementation of fmt.Stringer +// or error, +func indirectToStringerOrError(a interface{}) interface{} { + if a == nil { + return nil + } + + var errorType = reflect.TypeOf((*error)(nil)).Elem() + var fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() + + v := reflect.ValueOf(a) + for !v.Type().Implements(fmtStringerType) && !v.Type().Implements(errorType) && v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + return v.Interface() +} + +// ToStringE casts an interface to a string type. +func ToStringE(i interface{}) (string, error) { + i = indirectToStringerOrError(i) + + switch s := i.(type) { + case string: + return s, nil + case bool: + return strconv.FormatBool(s), nil + case float64: + return strconv.FormatFloat(s, 'f', -1, 64), nil + case float32: + return strconv.FormatFloat(float64(s), 'f', -1, 32), nil + case int: + return strconv.Itoa(s), nil + case int64: + return strconv.FormatInt(s, 10), nil + case int32: + return strconv.Itoa(int(s)), nil + case int16: + return strconv.FormatInt(int64(s), 10), nil + case int8: + return strconv.FormatInt(int64(s), 10), nil + case uint: + return strconv.FormatInt(int64(s), 10), nil + case uint64: + return strconv.FormatInt(int64(s), 10), nil + case uint32: + return strconv.FormatInt(int64(s), 10), nil + case uint16: + return strconv.FormatInt(int64(s), 10), nil + case uint8: + return strconv.FormatInt(int64(s), 10), nil + case []byte: + return string(s), nil + case template.HTML: + return string(s), nil + case template.URL: + return string(s), nil + case template.JS: + return string(s), nil + case template.CSS: + return string(s), nil + case template.HTMLAttr: + return string(s), nil + case nil: + return "", nil + case fmt.Stringer: + return s.String(), nil + case error: + return s.Error(), nil + default: + return "", fmt.Errorf("unable to cast %#v of type %T to string", i, i) + } +} + +// ToStringMapStringE casts an interface to a map[string]string type. +func ToStringMapStringE(i interface{}) (map[string]string, error) { + var m = map[string]string{} + + switch v := i.(type) { + case map[string]string: + return v, nil + case map[string]interface{}: + for k, val := range v { + m[ToString(k)] = ToString(val) + } + return m, nil + case map[interface{}]string: + for k, val := range v { + m[ToString(k)] = ToString(val) + } + return m, nil + case map[interface{}]interface{}: + for k, val := range v { + m[ToString(k)] = ToString(val) + } + return m, nil + case string: + err := jsonStringToObject(v, &m) + return m, err + default: + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]string", i, i) + } +} + +// ToStringMapStringSliceE casts an interface to a map[string][]string type. +func ToStringMapStringSliceE(i interface{}) (map[string][]string, error) { + var m = map[string][]string{} + + switch v := i.(type) { + case map[string][]string: + return v, nil + case map[string][]interface{}: + for k, val := range v { + m[ToString(k)] = ToStringSlice(val) + } + return m, nil + case map[string]string: + for k, val := range v { + m[ToString(k)] = []string{val} + } + case map[string]interface{}: + for k, val := range v { + switch vt := val.(type) { + case []interface{}: + m[ToString(k)] = ToStringSlice(vt) + case []string: + m[ToString(k)] = vt + default: + m[ToString(k)] = []string{ToString(val)} + } + } + return m, nil + case map[interface{}][]string: + for k, val := range v { + m[ToString(k)] = ToStringSlice(val) + } + return m, nil + case map[interface{}]string: + for k, val := range v { + m[ToString(k)] = ToStringSlice(val) + } + return m, nil + case map[interface{}][]interface{}: + for k, val := range v { + m[ToString(k)] = ToStringSlice(val) + } + return m, nil + case map[interface{}]interface{}: + for k, val := range v { + key, err := ToStringE(k) + if err != nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string][]string", i, i) + } + value, err := ToStringSliceE(val) + if err != nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string][]string", i, i) + } + m[key] = value + } + case string: + err := jsonStringToObject(v, &m) + return m, err + default: + return m, fmt.Errorf("unable to cast %#v of type %T to map[string][]string", i, i) + } + return m, nil +} + +// ToStringMapBoolE casts an interface to a map[string]bool type. +func ToStringMapBoolE(i interface{}) (map[string]bool, error) { + var m = map[string]bool{} + + switch v := i.(type) { + case map[interface{}]interface{}: + for k, val := range v { + m[ToString(k)] = ToBool(val) + } + return m, nil + case map[string]interface{}: + for k, val := range v { + m[ToString(k)] = ToBool(val) + } + return m, nil + case map[string]bool: + return v, nil + case string: + err := jsonStringToObject(v, &m) + return m, err + default: + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]bool", i, i) + } +} + +// ToStringMapE casts an interface to a map[string]interface{} type. +func ToStringMapE(i interface{}) (map[string]interface{}, error) { + var m = map[string]interface{}{} + + switch v := i.(type) { + case map[interface{}]interface{}: + for k, val := range v { + m[ToString(k)] = val + } + return m, nil + case map[string]interface{}: + return v, nil + case string: + err := jsonStringToObject(v, &m) + return m, err + default: + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]interface{}", i, i) + } +} + +// ToStringMapIntE casts an interface to a map[string]int{} type. +func ToStringMapIntE(i interface{}) (map[string]int, error) { + var m = map[string]int{} + if i == nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int", i, i) + } + + switch v := i.(type) { + case map[interface{}]interface{}: + for k, val := range v { + m[ToString(k)] = ToInt(val) + } + return m, nil + case map[string]interface{}: + for k, val := range v { + m[k] = ToInt(val) + } + return m, nil + case map[string]int: + return v, nil + case string: + err := jsonStringToObject(v, &m) + return m, err + } + + if reflect.TypeOf(i).Kind() != reflect.Map { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int", i, i) + } + + mVal := reflect.ValueOf(m) + v := reflect.ValueOf(i) + for _, keyVal := range v.MapKeys() { + val, err := ToIntE(v.MapIndex(keyVal).Interface()) + if err != nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int", i, i) + } + mVal.SetMapIndex(keyVal, reflect.ValueOf(val)) + } + return m, nil +} + +// ToStringMapInt64E casts an interface to a map[string]int64{} type. +func ToStringMapInt64E(i interface{}) (map[string]int64, error) { + var m = map[string]int64{} + if i == nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int64", i, i) + } + + switch v := i.(type) { + case map[interface{}]interface{}: + for k, val := range v { + m[ToString(k)] = ToInt64(val) + } + return m, nil + case map[string]interface{}: + for k, val := range v { + m[k] = ToInt64(val) + } + return m, nil + case map[string]int64: + return v, nil + case string: + err := jsonStringToObject(v, &m) + return m, err + } + + if reflect.TypeOf(i).Kind() != reflect.Map { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int64", i, i) + } + mVal := reflect.ValueOf(m) + v := reflect.ValueOf(i) + for _, keyVal := range v.MapKeys() { + val, err := ToInt64E(v.MapIndex(keyVal).Interface()) + if err != nil { + return m, fmt.Errorf("unable to cast %#v of type %T to map[string]int64", i, i) + } + mVal.SetMapIndex(keyVal, reflect.ValueOf(val)) + } + return m, nil +} + +// ToSliceE casts an interface to a []interface{} type. +func ToSliceE(i interface{}) ([]interface{}, error) { + var s []interface{} + + switch v := i.(type) { + case []interface{}: + return append(s, v...), nil + case []map[string]interface{}: + for _, u := range v { + s = append(s, u) + } + return s, nil + default: + return s, fmt.Errorf("unable to cast %#v of type %T to []interface{}", i, i) + } +} + +// ToBoolSliceE casts an interface to a []bool type. +func ToBoolSliceE(i interface{}) ([]bool, error) { + if i == nil { + return []bool{}, fmt.Errorf("unable to cast %#v of type %T to []bool", i, i) + } + + switch v := i.(type) { + case []bool: + return v, nil + } + + kind := reflect.TypeOf(i).Kind() + switch kind { + case reflect.Slice, reflect.Array: + s := reflect.ValueOf(i) + a := make([]bool, s.Len()) + for j := 0; j < s.Len(); j++ { + val, err := ToBoolE(s.Index(j).Interface()) + if err != nil { + return []bool{}, fmt.Errorf("unable to cast %#v of type %T to []bool", i, i) + } + a[j] = val + } + return a, nil + default: + return []bool{}, fmt.Errorf("unable to cast %#v of type %T to []bool", i, i) + } +} + +// ToStringSliceE casts an interface to a []string type. +func ToStringSliceE(i interface{}) ([]string, error) { + var a []string + + switch v := i.(type) { + case []interface{}: + for _, u := range v { + a = append(a, ToString(u)) + } + return a, nil + case []string: + return v, nil + case string: + return strings.Fields(v), nil + case interface{}: + str, err := ToStringE(v) + if err != nil { + return a, fmt.Errorf("unable to cast %#v of type %T to []string", i, i) + } + return []string{str}, nil + default: + return a, fmt.Errorf("unable to cast %#v of type %T to []string", i, i) + } +} + +// ToIntSliceE casts an interface to a []int type. +func ToIntSliceE(i interface{}) ([]int, error) { + if i == nil { + return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i) + } + + switch v := i.(type) { + case []int: + return v, nil + } + + kind := reflect.TypeOf(i).Kind() + switch kind { + case reflect.Slice, reflect.Array: + s := reflect.ValueOf(i) + a := make([]int, s.Len()) + for j := 0; j < s.Len(); j++ { + val, err := ToIntE(s.Index(j).Interface()) + if err != nil { + return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i) + } + a[j] = val + } + return a, nil + default: + return []int{}, fmt.Errorf("unable to cast %#v of type %T to []int", i, i) + } +} + +// ToDurationSliceE casts an interface to a []time.Duration type. +func ToDurationSliceE(i interface{}) ([]time.Duration, error) { + if i == nil { + return []time.Duration{}, fmt.Errorf("unable to cast %#v of type %T to []time.Duration", i, i) + } + + switch v := i.(type) { + case []time.Duration: + return v, nil + } + + kind := reflect.TypeOf(i).Kind() + switch kind { + case reflect.Slice, reflect.Array: + s := reflect.ValueOf(i) + a := make([]time.Duration, s.Len()) + for j := 0; j < s.Len(); j++ { + val, err := ToDurationE(s.Index(j).Interface()) + if err != nil { + return []time.Duration{}, fmt.Errorf("unable to cast %#v of type %T to []time.Duration", i, i) + } + a[j] = val + } + return a, nil + default: + return []time.Duration{}, fmt.Errorf("unable to cast %#v of type %T to []time.Duration", i, i) + } +} + +// StringToDate attempts to parse a string into a time.Time type using a +// predefined list of formats. If no suitable format is found, an error is +// returned. +func StringToDate(s string) (time.Time, error) { + return parseDateWith(s, []string{ + time.RFC3339, + "2006-01-02T15:04:05", // iso8601 without timezone + time.RFC1123Z, + time.RFC1123, + time.RFC822Z, + time.RFC822, + time.RFC850, + time.ANSIC, + time.UnixDate, + time.RubyDate, + "2006-01-02 15:04:05.999999999 -0700 MST", // Time.String() + "2006-01-02", + "02 Jan 2006", + "2006-01-02T15:04:05-0700", // RFC3339 without timezone hh:mm colon + "2006-01-02 15:04:05 -07:00", + "2006-01-02 15:04:05 -0700", + "2006-01-02 15:04:05Z07:00", // RFC3339 without T + "2006-01-02 15:04:05Z0700", // RFC3339 without T or timezone hh:mm colon + "2006-01-02 15:04:05", + time.Kitchen, + time.Stamp, + time.StampMilli, + time.StampMicro, + time.StampNano, + }) +} + +func parseDateWith(s string, dates []string) (d time.Time, e error) { + for _, dateType := range dates { + if d, e = time.Parse(dateType, s); e == nil { + return + } + } + return d, fmt.Errorf("unable to parse date: %s", s) +} + +// jsonStringToObject attempts to unmarshall a string as JSON into +// the object passed as pointer. +func jsonStringToObject(s string, v interface{}) error { + data := []byte(s) + return json.Unmarshal(data, v) +} diff --git a/vendor/github.com/spf13/cast/go.mod b/vendor/github.com/spf13/cast/go.mod new file mode 100644 index 000000000..c1c0232dd --- /dev/null +++ b/vendor/github.com/spf13/cast/go.mod @@ -0,0 +1,7 @@ +module github.com/spf13/cast + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.2 +) diff --git a/vendor/github.com/spf13/cast/go.sum b/vendor/github.com/spf13/cast/go.sum new file mode 100644 index 000000000..e03ee77d9 --- /dev/null +++ b/vendor/github.com/spf13/cast/go.sum @@ -0,0 +1,6 @@ +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=