Cleanup internal, vendors, cleanup cmd/*
Introduces /pkg for non-intenral packages
This commit is contained in:
parent
b66ed81136
commit
5a9bce44e8
20
.env.sample
20
.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" <info@example.tld>
|
||||
|
||||
SUBSCRIPTION_KEY=E7ox7cDMmBzsFS15Ub43KKdbBg6gqOYiUhK3nRN0BlpNzt88mHLycahhVfrJCccc
|
||||
SUBSCRIPTION_DOMAIN=local.crust.tech
|
||||
SMTP_FROM="Corteza" <info@example.tld>
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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.***
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -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.***
|
||||
|
||||
|
||||
23
Gopkg.lock
generated
23
Gopkg.lock
generated
@ -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",
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
17
cmd/monolith/main.go
Normal file
17
cmd/monolith/main.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
// }
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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")
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
34
internal/http/default.go
Normal file
34
internal/http/default.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -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(),
|
||||
|
||||
@ -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 "<host>:<port>" 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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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, "")
|
||||
})
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -3,7 +3,7 @@ package permissions
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/crusttech/crust/internal/test"
|
||||
"github.com/cortezaproject/corteza-server/internal/test"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/crusttech/crust/internal/test"
|
||||
"github.com/cortezaproject/corteza-server/internal/test"
|
||||
)
|
||||
|
||||
// Test role inheritance
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
195
monolith/monolith.go
Normal file
195
monolith/monolith.go
Normal file
@ -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)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package middleware
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
37
pkg/api/debug.go
Normal file
37
pkg/api/debug.go
Normal file
@ -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, "")
|
||||
})
|
||||
}
|
||||
@ -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()),
|
||||
)
|
||||
}()
|
||||
|
||||
24
pkg/api/metrics.go
Normal file
24
pkg/api/metrics.go
Normal file
@ -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())
|
||||
})
|
||||
}
|
||||
24
pkg/api/middleware.go
Normal file
24
pkg/api/middleware.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
129
pkg/api/server.go
Normal file
129
pkg/api/server.go
Normal file
@ -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
|
||||
}
|
||||
16
pkg/cli/context.go
Normal file
16
pkg/cli/context.go
Normal file
@ -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())
|
||||
}
|
||||
26
pkg/cli/flags/db.go
Normal file
26
pkg/cli/flags/db.go
Normal file
@ -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
|
||||
}
|
||||
56
pkg/cli/flags/flags.go
Normal file
56
pkg/cli/flags/flags.go
Normal file
@ -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)
|
||||
}
|
||||
71
pkg/cli/flags/http.go
Normal file
71
pkg/cli/flags/http.go
Normal file
@ -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
|
||||
}
|
||||
28
pkg/cli/flags/http_client.go
Normal file
28
pkg/cli/flags/http_client.go
Normal file
@ -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
|
||||
}
|
||||
29
pkg/cli/flags/jwt.go
Normal file
29
pkg/cli/flags/jwt.go
Normal file
@ -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
|
||||
}
|
||||
26
pkg/cli/flags/log.go
Normal file
26
pkg/cli/flags/log.go
Normal file
@ -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
|
||||
}
|
||||
21
pkg/cli/flags/monitor.go
Normal file
21
pkg/cli/flags/monitor.go
Normal file
@ -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
|
||||
}
|
||||
21
pkg/cli/flags/provision.go
Normal file
21
pkg/cli/flags/provision.go
Normal file
@ -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
|
||||
}
|
||||
58
pkg/cli/flags/pubsub.go
Normal file
58
pkg/cli/flags/pubsub.go
Normal file
@ -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
|
||||
}
|
||||
41
pkg/cli/flags/smtp.go
Normal file
41
pkg/cli/flags/smtp.go
Normal file
@ -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
|
||||
}
|
||||
39
pkg/cli/flags/websocket.go
Normal file
39
pkg/cli/flags/websocket.go
Normal file
@ -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
|
||||
}
|
||||
107
pkg/cli/helpers.go
Normal file
107
pkg/cli/helpers.go
Normal file
@ -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,
|
||||
)
|
||||
}
|
||||
202
vendor/github.com/crusttech/permit/LICENCE
generated
vendored
202
vendor/github.com/crusttech/permit/LICENCE
generated
vendored
@ -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.
|
||||
70
vendor/github.com/crusttech/permit/pkg/permit/client.go
generated
vendored
70
vendor/github.com/crusttech/permit/pkg/permit/client.go
generated
vendored
@ -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
|
||||
}
|
||||
35
vendor/github.com/crusttech/permit/pkg/permit/permit.go
generated
vendored
35
vendor/github.com/crusttech/permit/pkg/permit/permit.go
generated
vendored
@ -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())
|
||||
}
|
||||
199
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
generated
vendored
Normal file
199
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
generated
vendored
Normal file
@ -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}}
|
||||
}
|
||||
}
|
||||
181
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go
generated
vendored
Normal file
181
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
||||
44
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go
generated
vendored
Normal file
44
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
||||
311
vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
generated
vendored
Normal file
311
vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
generated
vendored
Normal file
@ -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,
|
||||
)
|
||||
}
|
||||
97
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
generated
vendored
Normal file
97
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
generated
vendored
Normal file
@ -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
|
||||
})
|
||||
}
|
||||
144
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go
generated
vendored
Normal file
144
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go
generated
vendored
Normal file
@ -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)
|
||||
})
|
||||
}
|
||||
447
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
generated
vendored
Normal file
447
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
25
vendor/github.com/spf13/cast/.gitignore
generated
vendored
Normal file
25
vendor/github.com/spf13/cast/.gitignore
generated
vendored
Normal file
@ -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
|
||||
15
vendor/github.com/spf13/cast/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/spf13/cast/.travis.yml
generated
vendored
Normal file
@ -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
|
||||
21
vendor/github.com/spf13/cast/LICENSE
generated
vendored
Normal file
21
vendor/github.com/spf13/cast/LICENSE
generated
vendored
Normal file
@ -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.
|
||||
38
vendor/github.com/spf13/cast/Makefile
generated
vendored
Normal file
38
vendor/github.com/spf13/cast/Makefile
generated
vendored
Normal file
@ -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}'
|
||||
75
vendor/github.com/spf13/cast/README.md
generated
vendored
Normal file
75
vendor/github.com/spf13/cast/README.md
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
cast
|
||||
====
|
||||
[](https://godoc.org/github.com/spf13/cast)
|
||||
[](https://travis-ci.org/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
|
||||
|
||||
171
vendor/github.com/spf13/cast/cast.go
generated
vendored
Normal file
171
vendor/github.com/spf13/cast/cast.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright © 2014 Steve Francia <spf@spf13.com>.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
1249
vendor/github.com/spf13/cast/caste.go
generated
vendored
Normal file
1249
vendor/github.com/spf13/cast/caste.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
vendor/github.com/spf13/cast/go.mod
generated
vendored
Normal file
7
vendor/github.com/spf13/cast/go.mod
generated
vendored
Normal file
@ -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
|
||||
)
|
||||
6
vendor/github.com/spf13/cast/go.sum
generated
vendored
Normal file
6
vendor/github.com/spf13/cast/go.sum
generated
vendored
Normal file
@ -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=
|
||||
Loading…
x
Reference in New Issue
Block a user