diff --git a/.circleci/config.yml b/.circleci/config.yml index 4b717083853..d3e6c71b520 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,6 +93,22 @@ jobs: - scripts/*.sh - scripts/publish + build-enterprise: + docker: + - image: grafana/build-container:v0.1 + working_directory: /go/src/github.com/grafana/grafana + steps: + - checkout + - run: + name: build and package grafana + command: './scripts/build/build_enterprise.sh' + - run: + name: sign packages + command: './scripts/build/sign_packages.sh' + - run: + name: sha-sum packages + command: 'go run build.go sha-dist' + deploy-master: docker: - image: circleci/python:2.7-stretch @@ -176,3 +192,7 @@ workflows: ignore: /.*/ tags: only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ + - build-enterprise: + filters: + tags: + only: /.*/ diff --git a/.gitignore b/.gitignore index 974fb618af9..cf13dac6d9b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ profile.cov /pkg/cmd/grafana-cli/grafana-cli /pkg/cmd/grafana-server/grafana-server /pkg/cmd/grafana-server/debug +/pkg/extensions debug.test /examples/*/dist /packaging/**/*.rpm diff --git a/Gopkg.lock b/Gopkg.lock index a35f5b23cda..3a7466c312a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -27,7 +27,37 @@ [[projects]] name = "github.com/aws/aws-sdk-go" - packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/ec2query","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/cloudwatch","service/ec2","service/ec2/ec2iface","service/s3","service/sts"] + packages = [ + "aws", + "aws/awserr", + "aws/awsutil", + "aws/client", + "aws/client/metadata", + "aws/corehandlers", + "aws/credentials", + "aws/credentials/ec2rolecreds", + "aws/credentials/endpointcreds", + "aws/credentials/stscreds", + "aws/defaults", + "aws/ec2metadata", + "aws/endpoints", + "aws/request", + "aws/session", + "aws/signer/v4", + "internal/shareddefaults", + "private/protocol", + "private/protocol/ec2query", + "private/protocol/query", + "private/protocol/query/queryutil", + "private/protocol/rest", + "private/protocol/restxml", + "private/protocol/xml/xmlutil", + "service/cloudwatch", + "service/ec2", + "service/ec2/ec2iface", + "service/s3", + "service/sts" + ] revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28" version = "v1.12.67" @@ -75,7 +105,10 @@ [[projects]] name = "github.com/denisenkom/go-mssqldb" - packages = [".","internal/cp"] + packages = [ + ".", + "internal/cp" + ] revision = "270bc3860bb94dd3a3ffd047377d746c5e276726" [[projects]] @@ -117,7 +150,12 @@ [[projects]] branch = "master" name = "github.com/go-macaron/session" - packages = [".","memcache","postgres","redis"] + packages = [ + ".", + "memcache", + "postgres", + "redis" + ] revision = "b8e286a0dba8f4999042d6b258daf51b31d08938" [[projects]] @@ -152,7 +190,13 @@ [[projects]] branch = "master" name = "github.com/golang/protobuf" - packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"] + packages = [ + "proto", + "ptypes", + "ptypes/any", + "ptypes/duration", + "ptypes/timestamp" + ] revision = "c65a0412e71e8b9b3bfd22925720d23c0f054237" [[projects]] @@ -221,7 +265,10 @@ [[projects]] name = "github.com/klauspost/compress" - packages = ["flate","gzip"] + packages = [ + "flate", + "gzip" + ] revision = "6c8db69c4b49dd4df1fff66996cf556176d0b9bf" version = "v1.2.1" @@ -252,7 +299,10 @@ [[projects]] branch = "master" name = "github.com/lib/pq" - packages = [".","oid"] + packages = [ + ".", + "oid" + ] revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345" [[projects]] @@ -287,7 +337,11 @@ [[projects]] name = "github.com/opentracing/opentracing-go" - packages = [".","ext","log"] + packages = [ + ".", + "ext", + "log" + ] revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38" version = "v1.0.2" @@ -297,9 +351,20 @@ revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0" version = "v2.1.0" +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + [[projects]] name = "github.com/prometheus/client_golang" - packages = ["api","api/prometheus/v1","prometheus","prometheus/promhttp"] + packages = [ + "api", + "api/prometheus/v1", + "prometheus", + "prometheus/promhttp" + ] revision = "967789050ba94deca04a5e84cce8ad472ce313c1" version = "v0.9.0-pre1" @@ -312,13 +377,22 @@ [[projects]] branch = "master" name = "github.com/prometheus/common" - packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"] + packages = [ + "expfmt", + "internal/bitbucket.org/ww/goautoneg", + "model" + ] revision = "89604d197083d4781071d3c65855d24ecfb0a563" [[projects]] branch = "master" name = "github.com/prometheus/procfs" - packages = [".","internal/util","nfsd","xfs"] + packages = [ + ".", + "internal/util", + "nfsd", + "xfs" + ] revision = "85fadb6e89903ef7cca6f6a804474cd5ea85b6e1" [[projects]] @@ -335,13 +409,21 @@ [[projects]] name = "github.com/smartystreets/assertions" - packages = [".","internal/go-render/render","internal/oglematchers"] + packages = [ + ".", + "internal/go-render/render", + "internal/oglematchers" + ] revision = "0b37b35ec7434b77e77a4bb29b79677cced992ea" version = "1.8.1" [[projects]] name = "github.com/smartystreets/goconvey" - packages = ["convey","convey/gotest","convey/reporting"] + packages = [ + "convey", + "convey/gotest", + "convey/reporting" + ] revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857" version = "1.6.3" @@ -353,7 +435,21 @@ [[projects]] name = "github.com/uber/jaeger-client-go" - packages = [".","config","internal/baggage","internal/baggage/remote","internal/spanlog","log","rpcmetrics","thrift-gen/agent","thrift-gen/baggage","thrift-gen/jaeger","thrift-gen/sampling","thrift-gen/zipkincore","utils"] + packages = [ + ".", + "config", + "internal/baggage", + "internal/baggage/remote", + "internal/spanlog", + "log", + "rpcmetrics", + "thrift-gen/agent", + "thrift-gen/baggage", + "thrift-gen/jaeger", + "thrift-gen/sampling", + "thrift-gen/zipkincore", + "utils" + ] revision = "3ac96c6e679cb60a74589b0d0aa7c70a906183f7" version = "v2.11.2" @@ -365,7 +461,10 @@ [[projects]] name = "github.com/yudai/gojsondiff" - packages = [".","formatter"] + packages = [ + ".", + "formatter" + ] revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6" version = "1.0.0" @@ -378,19 +477,37 @@ [[projects]] branch = "master" name = "golang.org/x/crypto" - packages = ["md4","pbkdf2"] + packages = [ + "md4", + "pbkdf2" + ] revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b" [[projects]] branch = "master" name = "golang.org/x/net" - packages = ["context","context/ctxhttp","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"] + packages = [ + "context", + "context/ctxhttp", + "http2", + "http2/hpack", + "idna", + "internal/timeseries", + "lex/httplex", + "trace" + ] revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" [[projects]] branch = "master" name = "golang.org/x/oauth2" - packages = [".","google","internal","jws","jwt"] + packages = [ + ".", + "google", + "internal", + "jws", + "jwt" + ] revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067" [[projects]] @@ -408,12 +525,39 @@ [[projects]] branch = "master" name = "golang.org/x/text" - packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"] + packages = [ + "collate", + "collate/build", + "internal/colltab", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "secure/bidirule", + "transform", + "unicode/bidi", + "unicode/cldr", + "unicode/norm", + "unicode/rangetable" + ] revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3" [[projects]] name = "google.golang.org/appengine" - packages = [".","cloudsql","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"] + packages = [ + ".", + "cloudsql", + "internal", + "internal/app_identity", + "internal/base", + "internal/datastore", + "internal/log", + "internal/modules", + "internal/remote_api", + "internal/urlfetch", + "urlfetch" + ] revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" version = "v1.0.0" @@ -425,7 +569,32 @@ [[projects]] name = "google.golang.org/grpc" - packages = [".","balancer","balancer/base","balancer/roundrobin","codes","connectivity","credentials","encoding","grpclb/grpc_lb_v1/messages","grpclog","health","health/grpc_health_v1","internal","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/passthrough","stats","status","tap","transport"] + packages = [ + ".", + "balancer", + "balancer/base", + "balancer/roundrobin", + "codes", + "connectivity", + "credentials", + "encoding", + "grpclb/grpc_lb_v1/messages", + "grpclog", + "health", + "health/grpc_health_v1", + "internal", + "keepalive", + "metadata", + "naming", + "peer", + "resolver", + "resolver/dns", + "resolver/passthrough", + "stats", + "status", + "tap", + "transport" + ] revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef" version = "v1.9.2" @@ -480,6 +649,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "ad3c71fd3244369c313978e9e7464c7116faee764386439a17de0707a08103aa" + inputs-digest = "2bd5b309496d57e2189a1cc28f5c1c41398c19729ba0cf53c8cbb17ea3f706b5" solver-name = "gps-cdcl" solver-version = 1 diff --git a/build.go b/build.go index 1bdbf4aac5c..7e7183b8b83 100644 --- a/build.go +++ b/build.go @@ -41,6 +41,7 @@ var ( buildNumber int = 0 binaries []string = []string{"grafana-server", "grafana-cli"} isDev bool = false + enterprise bool = false ) func main() { @@ -58,6 +59,7 @@ func main() { flag.StringVar(&phjsToRelease, "phjs", "", "PhantomJS binary") flag.BoolVar(&race, "race", race, "Use race detector") flag.BoolVar(&includeBuildNumber, "includeBuildNumber", includeBuildNumber, "IncludeBuildNumber in package name") + flag.BoolVar(&enterprise, "enterprise", enterprise, "Build enterprise version of Grafana") flag.IntVar(&buildNumber, "buildNumber", 0, "Build number from CI system") flag.BoolVar(&isDev, "dev", isDev, "optimal for development, skips certain steps") flag.Parse() @@ -283,19 +285,33 @@ func createPackage(options linuxPackageOptions) { "-s", "dir", "--description", "Grafana", "-C", packageRoot, - "--vendor", "Grafana", "--url", "https://grafana.com", - "--license", "\"Apache 2.0\"", "--maintainer", "contact@grafana.com", "--config-files", options.initdScriptFilePath, "--config-files", options.etcDefaultFilePath, "--config-files", options.systemdServiceFilePath, "--after-install", options.postinstSrc, - "--name", "grafana", + "--version", linuxPackageVersion, "-p", "./dist", } + name := "grafana" + if enterprise { + name += "-enterprise" + } + args = append(args, "--name", name) + + description := "Grafana" + if enterprise { + description += " Enterprise" + } + args = append(args, "--vendor", description) + + if !enterprise { + args = append(args, "--license", "\"Apache 2.0\"") + } + if options.packageType == "rpm" { args = append(args, "--rpm-posttrans", "packaging/rpm/control/posttrans") } @@ -412,6 +428,7 @@ func ldflags() string { b.WriteString(fmt.Sprintf(" -X main.version=%s", version)) b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha())) b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp())) + b.WriteString(fmt.Sprintf(" -X main.enterprise=%t", enterprise)) return b.String() } diff --git a/pkg/api/api.go b/pkg/api/api.go index 96b764b95b9..493f9eb9d01 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -23,7 +23,7 @@ func (hs *HTTPServer) registerRoutes() { // automatically set HEAD for every GET macaronR.SetAutoHead(true) - r := newRouteRegister(middleware.RequestMetrics, middleware.RequestTracing) + r := hs.RouteRegister // not logged in views r.Get("/", reqSignedIn, Index) diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 8e01e869329..8d1d0dc0a60 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -35,15 +35,14 @@ type HTTPServer struct { context context.Context streamManager *live.StreamManager cache *gocache.Cache + RouteRegister RouteRegister `inject:""` httpSrv *http.Server } -func NewHTTPServer() *HTTPServer { - return &HTTPServer{ - log: log.New("http.server"), - cache: gocache.New(5*time.Minute, 10*time.Minute), - } +func (hs *HTTPServer) Init() { + hs.log = log.New("http.server") + hs.cache = gocache.New(5*time.Minute, 10*time.Minute) } func (hs *HTTPServer) Start(ctx context.Context) error { diff --git a/pkg/api/index.go b/pkg/api/index.go index 94094706f68..7c954f89ada 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -289,7 +289,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { data.NavTree = append(data.NavTree, &dtos.NavLink{ Text: "Help", - SubTitle: fmt.Sprintf(`Grafana v%s (%s)`, setting.BuildVersion, setting.BuildCommit), + SubTitle: fmt.Sprintf(`%s v%s (%s)`, setting.ApplicationName, setting.BuildVersion, setting.BuildCommit), Id: "help", Url: "#", Icon: "gicon gicon-question", diff --git a/pkg/api/route_register.go b/pkg/api/route_register.go index 76ebb633ca1..926de13c546 100644 --- a/pkg/api/route_register.go +++ b/pkg/api/route_register.go @@ -11,6 +11,8 @@ type Router interface { Get(pattern string, handlers ...macaron.Handler) *macaron.Route } +// RouteRegister allows you to add routes and macaron.Handlers +// that the web server should serve. type RouteRegister interface { Get(string, ...macaron.Handler) Post(string, ...macaron.Handler) @@ -26,7 +28,8 @@ type RouteRegister interface { type RegisterNamedMiddleware func(name string) macaron.Handler -func newRouteRegister(namedMiddleware ...RegisterNamedMiddleware) RouteRegister { +// NewRouteRegister creates a new RouteRegister with all middlewares sent as params +func NewRouteRegister(namedMiddleware ...RegisterNamedMiddleware) RouteRegister { return &routeRegister{ prefix: "", routes: []route{}, diff --git a/pkg/api/route_register_test.go b/pkg/api/route_register_test.go index f8a043c48df..3b5d79599a8 100644 --- a/pkg/api/route_register_test.go +++ b/pkg/api/route_register_test.go @@ -51,7 +51,7 @@ func TestRouteSimpleRegister(t *testing.T) { } // Setup - rr := newRouteRegister(func(name string) macaron.Handler { + rr := NewRouteRegister(func(name string) macaron.Handler { return emptyHandler(name) }) @@ -96,7 +96,7 @@ func TestRouteGroupedRegister(t *testing.T) { } // Setup - rr := newRouteRegister() + rr := NewRouteRegister() rr.Delete("/admin", emptyHandler("1")) rr.Get("/down", emptyHandler("1"), emptyHandler("2")) @@ -150,7 +150,7 @@ func TestNamedMiddlewareRouteRegister(t *testing.T) { } // Setup - rr := newRouteRegister(func(name string) macaron.Handler { + rr := NewRouteRegister(func(name string) macaron.Handler { return emptyHandler(name) }) diff --git a/pkg/bus/bus.go b/pkg/bus/bus.go index 59d4592766e..437796991a5 100644 --- a/pkg/bus/bus.go +++ b/pkg/bus/bus.go @@ -2,7 +2,7 @@ package bus import ( "context" - "fmt" + "errors" "reflect" ) @@ -10,6 +10,8 @@ type HandlerFunc interface{} type CtxHandlerFunc func() type Msg interface{} +var ErrHandlerNotFound = errors.New("handler not found") + type Bus interface { Dispatch(msg Msg) error DispatchCtx(ctx context.Context, msg Msg) error @@ -38,12 +40,17 @@ func New() Bus { return bus } +// Want to get rid of global bus +func GetBus() Bus { + return globalBus +} + func (b *InProcBus) DispatchCtx(ctx context.Context, msg Msg) error { var msgName = reflect.TypeOf(msg).Elem().Name() var handler = b.handlers[msgName] if handler == nil { - return fmt.Errorf("handler not found for %s", msgName) + return ErrHandlerNotFound } var params = make([]reflect.Value, 2) @@ -64,7 +71,7 @@ func (b *InProcBus) Dispatch(msg Msg) error { var handler = b.handlers[msgName] if handler == nil { - return fmt.Errorf("handler not found for %s", msgName) + return ErrHandlerNotFound } var params = make([]reflect.Value, 1) diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go index da99bc9ba40..466e97ff2d6 100644 --- a/pkg/cmd/grafana-server/main.go +++ b/pkg/cmd/grafana-server/main.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/setting" + _ "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/services/alerting/conditions" _ "github.com/grafana/grafana/pkg/services/alerting/notifiers" _ "github.com/grafana/grafana/pkg/tsdb/cloudwatch" @@ -33,6 +34,7 @@ import ( var version = "5.0.0" var commit = "NA" var buildstamp string +var enterprise string var configFile = flag.String("config", "", "path to config file") var homePath = flag.String("homepath", "", "path to grafana install/home path, defaults to working directory") @@ -76,6 +78,7 @@ func main() { setting.BuildVersion = version setting.BuildCommit = commit setting.BuildStamp = buildstampInt64 + setting.Enterprise, _ = strconv.ParseBool(enterprise) metrics.M_Grafana_Version.WithLabelValues(version).Set(1) shutdownCompleted := make(chan int) diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index b8387403161..1bf0e90915f 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -8,9 +8,15 @@ import ( "net" "os" "path/filepath" + "reflect" "strconv" "time" + "github.com/facebookgo/inject" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/provisioning" "golang.org/x/sync/errgroup" @@ -20,15 +26,17 @@ import ( "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/plugins" - "github.com/grafana/grafana/pkg/services/alerting" - "github.com/grafana/grafana/pkg/services/cleanup" "github.com/grafana/grafana/pkg/services/notifications" - "github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/social" "github.com/grafana/grafana/pkg/tracing" + + _ "github.com/grafana/grafana/pkg/extensions" + _ "github.com/grafana/grafana/pkg/services/alerting" + _ "github.com/grafana/grafana/pkg/services/cleanup" + _ "github.com/grafana/grafana/pkg/services/search" ) func NewGrafanaServer() *GrafanaServerImpl { @@ -48,18 +56,20 @@ type GrafanaServerImpl struct { shutdownFn context.CancelFunc childRoutines *errgroup.Group log log.Logger + RouteRegister api.RouteRegister `inject:""` - httpServer *api.HTTPServer + HttpServer *api.HTTPServer `inject:""` } func (g *GrafanaServerImpl) Start() error { g.initLogging() g.writePIDFile() - initSql() + // initSql + sqlstore.NewEngine() // TODO: this should return an error + sqlstore.EnsureAdminUser() metrics.Init(setting.Cfg) - search.Init() login.Init() social.NewOAuthService() @@ -79,28 +89,62 @@ func (g *GrafanaServerImpl) Start() error { } defer tracingCloser.Close() - // init alerting - if setting.AlertingEnabled && setting.ExecuteAlerts { - engine := alerting.NewEngine() - g.childRoutines.Go(func() error { return engine.Run(g.context) }) + if err = notifications.Init(); err != nil { + return fmt.Errorf("Notification service failed to initialize. error: %v", err) } - // cleanup service - cleanUpService := cleanup.NewCleanUpService() - g.childRoutines.Go(func() error { return cleanUpService.Run(g.context) }) + serviceGraph := inject.Graph{} + serviceGraph.Provide(&inject.Object{Value: bus.GetBus()}) + serviceGraph.Provide(&inject.Object{Value: dashboards.NewProvisioningService()}) + serviceGraph.Provide(&inject.Object{Value: api.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)}) + serviceGraph.Provide(&inject.Object{Value: api.HTTPServer{}}) + services := registry.GetServices() - if err = notifications.Init(); err != nil { - return fmt.Errorf("Notification service failed to initialize. error: %v", err) + // Add all services to dependency graph + for _, service := range services { + serviceGraph.Provide(&inject.Object{Value: service}) } - sendSystemdNotification("READY=1") + serviceGraph.Provide(&inject.Object{Value: g}) - return g.startHttpServer() -} + // Inject dependencies to services + if err := serviceGraph.Populate(); err != nil { + return fmt.Errorf("Failed to populate service dependency: %v", err) + } -func initSql() { - sqlstore.NewEngine() - sqlstore.EnsureAdminUser() + // Init & start services + for _, service := range services { + if registry.IsDisabled(service) { + continue + } + + g.log.Info("Initializing " + reflect.TypeOf(service).Elem().Name()) + + if err := service.Init(); err != nil { + return fmt.Errorf("Service init failed %v", err) + } + } + + // Start background services + for index := range services { + service, ok := services[index].(registry.BackgroundService) + if !ok { + continue + } + + if registry.IsDisabled(services[index]) { + continue + } + + g.childRoutines.Go(func() error { + err := service.Run(g.context) + g.log.Info("Stopped "+reflect.TypeOf(service).Elem().Name(), "reason", err) + return err + }) + } + + sendSystemdNotification("READY=1") + return g.startHttpServer() } func (g *GrafanaServerImpl) initLogging() { @@ -115,14 +159,14 @@ func (g *GrafanaServerImpl) initLogging() { os.Exit(1) } - g.log.Info("Starting Grafana", "version", version, "commit", commit, "compiled", time.Unix(setting.BuildStamp, 0)) + g.log.Info("Starting "+setting.ApplicationName, "version", version, "commit", commit, "compiled", time.Unix(setting.BuildStamp, 0)) setting.LogConfigurationInfo() } func (g *GrafanaServerImpl) startHttpServer() error { - g.httpServer = api.NewHTTPServer() + g.HttpServer.Init() - err := g.httpServer.Start(g.context) + err := g.HttpServer.Start(g.context) if err != nil { return fmt.Errorf("Fail to start server. error: %v", err) @@ -134,7 +178,7 @@ func (g *GrafanaServerImpl) startHttpServer() error { func (g *GrafanaServerImpl) Shutdown(code int, reason string) { g.log.Info("Shutdown started", "code", code, "reason", reason) - err := g.httpServer.Shutdown(g.context) + err := g.HttpServer.Shutdown(g.context) if err != nil { g.log.Error("Failed to shutdown server", "error", err) } diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go new file mode 100644 index 00000000000..34ac9da7e86 --- /dev/null +++ b/pkg/extensions/main.go @@ -0,0 +1,3 @@ +package extensions + +import _ "github.com/pkg/errors" diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 417f565dd0c..45e7c934bea 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -58,7 +58,7 @@ func (p *PluginManager) Run(ctx context.Context) error { p.Kill() } - p.log.Info("Stopped Plugins", "error", ctx.Err()) + p.log.Info("Stopped Plugins", "reason", ctx.Err()) return ctx.Err() } diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go new file mode 100644 index 00000000000..ba3229d6df6 --- /dev/null +++ b/pkg/registry/registry.go @@ -0,0 +1,33 @@ +package registry + +import ( + "context" +) + +var services = []Service{} + +func RegisterService(srv Service) { + services = append(services, srv) +} + +func GetServices() []Service { + return services +} + +type Service interface { + Init() error +} + +// Useful for alerting service +type CanBeDisabled interface { + IsDisabled() bool +} + +type BackgroundService interface { + Run(ctx context.Context) error +} + +func IsDisabled(srv Service) bool { + canBeDisabled, ok := srv.(CanBeDisabled) + return ok && canBeDisabled.IsDisabled() +} diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index 0945a2a5330..bdd8ff2cfe2 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -11,6 +11,8 @@ import ( "github.com/benbjohnson/clock" "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/setting" "golang.org/x/sync/errgroup" ) @@ -25,31 +27,37 @@ type Engine struct { resultHandler ResultHandler } -func NewEngine() *Engine { - e := &Engine{ - ticker: NewTicker(time.Now(), time.Second*0, clock.New()), - execQueue: make(chan *Job, 1000), - scheduler: NewScheduler(), - evalHandler: NewEvalHandler(), - ruleReader: NewRuleReader(), - log: log.New("alerting.engine"), - resultHandler: NewResultHandler(), - } +func init() { + registry.RegisterService(&Engine{}) +} +func NewEngine() *Engine { + e := &Engine{} + e.Init() return e } -func (e *Engine) Run(ctx context.Context) error { - e.log.Info("Initializing Alerting") +func (e *Engine) IsDisabled() bool { + return !setting.AlertingEnabled || !setting.ExecuteAlerts +} - alertGroup, ctx := errgroup.WithContext(ctx) +func (e *Engine) Init() error { + e.ticker = NewTicker(time.Now(), time.Second*0, clock.New()) + e.execQueue = make(chan *Job, 1000) + e.scheduler = NewScheduler() + e.evalHandler = NewEvalHandler() + e.ruleReader = NewRuleReader() + e.log = log.New("alerting.engine") + e.resultHandler = NewResultHandler() + return nil +} +func (e *Engine) Run(ctx context.Context) error { + alertGroup, ctx := errgroup.WithContext(ctx) alertGroup.Go(func() error { return e.alertingTicker(ctx) }) alertGroup.Go(func() error { return e.runJobDispatcher(ctx) }) err := alertGroup.Wait() - - e.log.Info("Stopped Alerting", "reason", err) return err } diff --git a/pkg/services/cleanup/cleanup.go b/pkg/services/cleanup/cleanup.go index 5e9efeea3b0..ef474fd2eb2 100644 --- a/pkg/services/cleanup/cleanup.go +++ b/pkg/services/cleanup/cleanup.go @@ -7,11 +7,10 @@ import ( "path" "time" - "golang.org/x/sync/errgroup" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/setting" ) @@ -19,24 +18,16 @@ type CleanUpService struct { log log.Logger } -func NewCleanUpService() *CleanUpService { - return &CleanUpService{ - log: log.New("cleanup"), - } +func init() { + registry.RegisterService(&CleanUpService{}) } -func (service *CleanUpService) Run(ctx context.Context) error { - service.log.Info("Initializing CleanUpService") - - g, _ := errgroup.WithContext(ctx) - g.Go(func() error { return service.start(ctx) }) - - err := g.Wait() - service.log.Info("Stopped CleanUpService", "reason", err) - return err +func (service *CleanUpService) Init() error { + service.log = log.New("cleanup") + return nil } -func (service *CleanUpService) start(ctx context.Context) error { +func (service *CleanUpService) Run(ctx context.Context) error { service.cleanUpTmpFiles() ticker := time.NewTicker(time.Minute * 10) diff --git a/pkg/services/search/handlers.go b/pkg/services/search/handlers.go index cf194c320bb..9d40697f489 100644 --- a/pkg/services/search/handlers.go +++ b/pkg/services/search/handlers.go @@ -5,13 +5,23 @@ import ( "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" ) -func Init() { - bus.AddHandler("search", searchHandler) +func init() { + registry.RegisterService(&SearchService{}) } -func searchHandler(query *Query) error { +type SearchService struct { + Bus bus.Bus `inject:""` +} + +func (s *SearchService) Init() error { + s.Bus.AddHandler(s.searchHandler) + return nil +} + +func (s *SearchService) searchHandler(query *Query) error { dashQuery := FindPersistedDashboardsQuery{ Title: query.Title, SignedInUser: query.SignedInUser, diff --git a/pkg/services/search/handlers_test.go b/pkg/services/search/handlers_test.go index fc223b2ef4b..5cf934cbc92 100644 --- a/pkg/services/search/handlers_test.go +++ b/pkg/services/search/handlers_test.go @@ -12,6 +12,7 @@ func TestSearch(t *testing.T) { Convey("Given search query", t, func() { query := Query{Limit: 2000, SignedInUser: &m.SignedInUser{IsGrafanaAdmin: true}} + ss := &SearchService{} bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error { query.Result = HitList{ @@ -35,7 +36,7 @@ func TestSearch(t *testing.T) { }) Convey("That is empty", func() { - err := searchHandler(&query) + err := ss.searchHandler(&query) So(err, ShouldBeNil) Convey("should return sorted results", func() { diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 782318fa188..e4be3208c86 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -77,7 +77,7 @@ func EnsureAdminUser() { log.Info("Created default admin user: %v", setting.AdminUser) } -func NewEngine() { +func NewEngine() *xorm.Engine { x, err := getEngine() if err != nil { @@ -91,6 +91,8 @@ func NewEngine() { sqlog.Error("Fail to initialize orm engine", "error", err) os.Exit(1) } + + return x } func SetEngine(engine *xorm.Engine) (err error) { diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 30a40602b1c..58a33b2202f 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -45,9 +45,11 @@ var ( InstanceName string // build - BuildVersion string - BuildCommit string - BuildStamp int64 + BuildVersion string + BuildCommit string + BuildStamp int64 + Enterprise bool + ApplicationName string // Paths LogsPath string @@ -486,6 +488,11 @@ func NewConfigContext(args *CommandLineArgs) error { return err } + ApplicationName = "Grafana" + if Enterprise { + ApplicationName += " Enterprise" + } + Env = Cfg.Section("").Key("app_mode").MustString("development") InstanceName = Cfg.Section("").Key("instance_name").MustString("unknown_instance_name") PluginsPath = makeAbsolute(Cfg.Section("paths").Key("plugins").String(), HomePath) diff --git a/scripts/build/build_enterprise.sh b/scripts/build/build_enterprise.sh new file mode 100755 index 00000000000..02d8c78c885 --- /dev/null +++ b/scripts/build/build_enterprise.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# +# This script is executed from within the container. +# + +echo "building enterprise version" + +GOPATH=/go +REPO_PATH=$GOPATH/src/github.com/grafana/grafana + + +cd /go/src/github.com/grafana/grafana +echo "current dir: $(pwd)" + +cd .. +git clone -b ee_build --single-branch git@github.com:grafana/grafana-enterprise.git --depth 10 +cd grafana-enterprise +git checkout 7fbae9c1be3467c4a39cf6ad85278a6896ceb49f +./build.sh + +cd ../grafana + +function exit_if_fail { + command=$@ + echo "Executing '$command'" + eval $command + rc=$? + if [ $rc -ne 0 ]; then + echo "'$command' returned $rc." + exit $rc + fi +} + +exit_if_fail go test ./pkg/extensions/... + + +if [ "$CIRCLE_TAG" != "" ]; then + echo "Building a release from tag $ls" + go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true -includeBuildNumber=false build +else + echo "Building incremental build for $CIRCLE_BRANCH" + go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true build +fi + +yarn install --pure-lockfile --no-progress + +source /etc/profile.d/rvm.sh + +echo "current dir: $(pwd)" + +if [ "$CIRCLE_TAG" != "" ]; then + echo "Packaging a release from tag $CIRCLE_TAG" + go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true -includeBuildNumber=false package latest +else + echo "Packaging incremental build for $CIRCLE_BRANCH" + go run build.go -buildNumber=${CIRCLE_BUILD_NUM} -enterprise=true package latest +fi diff --git a/vendor/github.com/facebookgo/inject/inject.go b/vendor/github.com/facebookgo/inject/inject.go new file mode 100644 index 00000000000..300b9a37622 --- /dev/null +++ b/vendor/github.com/facebookgo/inject/inject.go @@ -0,0 +1,576 @@ +// Package inject provides a reflect based injector. A large application built +// with dependency injection in mind will typically involve the boring work of +// setting up the object graph. This library attempts to take care of this +// boring work by creating and connecting the various objects. Its use involves +// you seeding the object graph with some (possibly incomplete) objects, where +// the underlying types have been tagged for injection. Given this, the +// library will populate the objects creating new ones as necessary. It uses +// singletons by default, supports optional private instances as well as named +// instances. +// +// It works using Go's reflection package and is inherently limited in what it +// can do as opposed to a code-gen system with respect to private fields. +// +// The usage pattern for the library involves struct tags. It requires the tag +// format used by the various standard libraries, like json, xml etc. It +// involves tags in one of the three forms below: +// +// `inject:""` +// `inject:"private"` +// `inject:"dev logger"` +// +// The first no value syntax is for the common case of a singleton dependency +// of the associated type. The second triggers creation of a private instance +// for the associated type. Finally the last form is asking for a named +// dependency called "dev logger". +package inject + +import ( + "bytes" + "fmt" + "math/rand" + "reflect" + + "github.com/facebookgo/structtag" +) + +// Logger allows for simple logging as inject traverses and populates the +// object graph. +type Logger interface { + Debugf(format string, v ...interface{}) +} + +// Populate is a short-hand for populating a graph with the given incomplete +// object values. +func Populate(values ...interface{}) error { + var g Graph + for _, v := range values { + if err := g.Provide(&Object{Value: v}); err != nil { + return err + } + } + return g.Populate() +} + +// An Object in the Graph. +type Object struct { + Value interface{} + Name string // Optional + Complete bool // If true, the Value will be considered complete + Fields map[string]*Object // Populated with the field names that were injected and their corresponding *Object. + reflectType reflect.Type + reflectValue reflect.Value + private bool // If true, the Value will not be used and will only be populated + created bool // If true, the Object was created by us + embedded bool // If true, the Object is an embedded struct provided internally +} + +// String representation suitable for human consumption. +func (o *Object) String() string { + var buf bytes.Buffer + fmt.Fprint(&buf, o.reflectType) + if o.Name != "" { + fmt.Fprintf(&buf, " named %s", o.Name) + } + return buf.String() +} + +func (o *Object) addDep(field string, dep *Object) { + if o.Fields == nil { + o.Fields = make(map[string]*Object) + } + o.Fields[field] = dep +} + +// The Graph of Objects. +type Graph struct { + Logger Logger // Optional, will trigger debug logging. + unnamed []*Object + unnamedType map[reflect.Type]bool + named map[string]*Object +} + +// Provide objects to the Graph. The Object documentation describes +// the impact of various fields. +func (g *Graph) Provide(objects ...*Object) error { + for _, o := range objects { + o.reflectType = reflect.TypeOf(o.Value) + o.reflectValue = reflect.ValueOf(o.Value) + + if o.Fields != nil { + return fmt.Errorf( + "fields were specified on object %s when it was provided", + o, + ) + } + + if o.Name == "" { + if !isStructPtr(o.reflectType) { + return fmt.Errorf( + "expected unnamed object value to be a pointer to a struct but got type %s "+ + "with value %v", + o.reflectType, + o.Value, + ) + } + + if !o.private { + if g.unnamedType == nil { + g.unnamedType = make(map[reflect.Type]bool) + } + + if g.unnamedType[o.reflectType] { + return fmt.Errorf( + "provided two unnamed instances of type *%s.%s", + o.reflectType.Elem().PkgPath(), o.reflectType.Elem().Name(), + ) + } + g.unnamedType[o.reflectType] = true + } + g.unnamed = append(g.unnamed, o) + } else { + if g.named == nil { + g.named = make(map[string]*Object) + } + + if g.named[o.Name] != nil { + return fmt.Errorf("provided two instances named %s", o.Name) + } + g.named[o.Name] = o + } + + if g.Logger != nil { + if o.created { + g.Logger.Debugf("created %s", o) + } else if o.embedded { + g.Logger.Debugf("provided embedded %s", o) + } else { + g.Logger.Debugf("provided %s", o) + } + } + } + return nil +} + +// Populate the incomplete Objects. +func (g *Graph) Populate() error { + for _, o := range g.named { + if o.Complete { + continue + } + + if err := g.populateExplicit(o); err != nil { + return err + } + } + + // We append and modify our slice as we go along, so we don't use a standard + // range loop, and do a single pass thru each object in our graph. + i := 0 + for { + if i == len(g.unnamed) { + break + } + + o := g.unnamed[i] + i++ + + if o.Complete { + continue + } + + if err := g.populateExplicit(o); err != nil { + return err + } + } + + // A Second pass handles injecting Interface values to ensure we have created + // all concrete types first. + for _, o := range g.unnamed { + if o.Complete { + continue + } + + if err := g.populateUnnamedInterface(o); err != nil { + return err + } + } + + for _, o := range g.named { + if o.Complete { + continue + } + + if err := g.populateUnnamedInterface(o); err != nil { + return err + } + } + + return nil +} + +func (g *Graph) populateExplicit(o *Object) error { + // Ignore named value types. + if o.Name != "" && !isStructPtr(o.reflectType) { + return nil + } + +StructLoop: + for i := 0; i < o.reflectValue.Elem().NumField(); i++ { + field := o.reflectValue.Elem().Field(i) + fieldType := field.Type() + fieldTag := o.reflectType.Elem().Field(i).Tag + fieldName := o.reflectType.Elem().Field(i).Name + tag, err := parseTag(string(fieldTag)) + if err != nil { + return fmt.Errorf( + "unexpected tag format `%s` for field %s in type %s", + string(fieldTag), + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Skip fields without a tag. + if tag == nil { + continue + } + + // Cannot be used with unexported fields. + if !field.CanSet() { + return fmt.Errorf( + "inject requested on unexported field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Inline tag on anything besides a struct is considered invalid. + if tag.Inline && fieldType.Kind() != reflect.Struct { + return fmt.Errorf( + "inline requested on non inlined field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Don't overwrite existing values. + if !isNilOrZero(field, fieldType) { + continue + } + + // Named injects must have been explicitly provided. + if tag.Name != "" { + existing := g.named[tag.Name] + if existing == nil { + return fmt.Errorf( + "did not find object named %s required by field %s in type %s", + tag.Name, + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + if !existing.reflectType.AssignableTo(fieldType) { + return fmt.Errorf( + "object named %s of type %s is not assignable to field %s (%s) in type %s", + tag.Name, + fieldType, + o.reflectType.Elem().Field(i).Name, + existing.reflectType, + o.reflectType, + ) + } + + field.Set(reflect.ValueOf(existing.Value)) + if g.Logger != nil { + g.Logger.Debugf( + "assigned %s to field %s in %s", + existing, + o.reflectType.Elem().Field(i).Name, + o, + ) + } + o.addDep(fieldName, existing) + continue StructLoop + } + + // Inline struct values indicate we want to traverse into it, but not + // inject itself. We require an explicit "inline" tag for this to work. + if fieldType.Kind() == reflect.Struct { + if tag.Private { + return fmt.Errorf( + "cannot use private inject on inline struct on field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + if !tag.Inline { + return fmt.Errorf( + "inline struct on field %s in type %s requires an explicit \"inline\" tag", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + err := g.Provide(&Object{ + Value: field.Addr().Interface(), + private: true, + embedded: o.reflectType.Elem().Field(i).Anonymous, + }) + if err != nil { + return err + } + continue + } + + // Interface injection is handled in a second pass. + if fieldType.Kind() == reflect.Interface { + continue + } + + // Maps are created and required to be private. + if fieldType.Kind() == reflect.Map { + if !tag.Private { + return fmt.Errorf( + "inject on map field %s in type %s must be named or private", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + field.Set(reflect.MakeMap(fieldType)) + if g.Logger != nil { + g.Logger.Debugf( + "made map for field %s in %s", + o.reflectType.Elem().Field(i).Name, + o, + ) + } + continue + } + + // Can only inject Pointers from here on. + if !isStructPtr(fieldType) { + return fmt.Errorf( + "found inject tag on unsupported field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Unless it's a private inject, we'll look for an existing instance of the + // same type. + if !tag.Private { + for _, existing := range g.unnamed { + if existing.private { + continue + } + if existing.reflectType.AssignableTo(fieldType) { + field.Set(reflect.ValueOf(existing.Value)) + if g.Logger != nil { + g.Logger.Debugf( + "assigned existing %s to field %s in %s", + existing, + o.reflectType.Elem().Field(i).Name, + o, + ) + } + o.addDep(fieldName, existing) + continue StructLoop + } + } + } + + newValue := reflect.New(fieldType.Elem()) + newObject := &Object{ + Value: newValue.Interface(), + private: tag.Private, + created: true, + } + + // Add the newly ceated object to the known set of objects. + err = g.Provide(newObject) + if err != nil { + return err + } + + // Finally assign the newly created object to our field. + field.Set(newValue) + if g.Logger != nil { + g.Logger.Debugf( + "assigned newly created %s to field %s in %s", + newObject, + o.reflectType.Elem().Field(i).Name, + o, + ) + } + o.addDep(fieldName, newObject) + } + return nil +} + +func (g *Graph) populateUnnamedInterface(o *Object) error { + // Ignore named value types. + if o.Name != "" && !isStructPtr(o.reflectType) { + return nil + } + + for i := 0; i < o.reflectValue.Elem().NumField(); i++ { + field := o.reflectValue.Elem().Field(i) + fieldType := field.Type() + fieldTag := o.reflectType.Elem().Field(i).Tag + fieldName := o.reflectType.Elem().Field(i).Name + tag, err := parseTag(string(fieldTag)) + if err != nil { + return fmt.Errorf( + "unexpected tag format `%s` for field %s in type %s", + string(fieldTag), + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Skip fields without a tag. + if tag == nil { + continue + } + + // We only handle interface injection here. Other cases including errors + // are handled in the first pass when we inject pointers. + if fieldType.Kind() != reflect.Interface { + continue + } + + // Interface injection can't be private because we can't instantiate new + // instances of an interface. + if tag.Private { + return fmt.Errorf( + "found private inject tag on interface field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + + // Don't overwrite existing values. + if !isNilOrZero(field, fieldType) { + continue + } + + // Named injects must have already been handled in populateExplicit. + if tag.Name != "" { + panic(fmt.Sprintf("unhandled named instance with name %s", tag.Name)) + } + + // Find one, and only one assignable value for the field. + var found *Object + for _, existing := range g.unnamed { + if existing.private { + continue + } + if existing.reflectType.AssignableTo(fieldType) { + if found != nil { + return fmt.Errorf( + "found two assignable values for field %s in type %s. one type "+ + "%s with value %v and another type %s with value %v", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + found.reflectType, + found.Value, + existing.reflectType, + existing.reflectValue, + ) + } + found = existing + field.Set(reflect.ValueOf(existing.Value)) + if g.Logger != nil { + g.Logger.Debugf( + "assigned existing %s to interface field %s in %s", + existing, + o.reflectType.Elem().Field(i).Name, + o, + ) + } + o.addDep(fieldName, existing) + } + } + + // If we didn't find an assignable value, we're missing something. + if found == nil { + return fmt.Errorf( + "found no assignable value for field %s in type %s", + o.reflectType.Elem().Field(i).Name, + o.reflectType, + ) + } + } + return nil +} + +// Objects returns all known objects, named as well as unnamed. The returned +// elements are not in a stable order. +func (g *Graph) Objects() []*Object { + objects := make([]*Object, 0, len(g.unnamed)+len(g.named)) + for _, o := range g.unnamed { + if !o.embedded { + objects = append(objects, o) + } + } + for _, o := range g.named { + if !o.embedded { + objects = append(objects, o) + } + } + // randomize to prevent callers from relying on ordering + for i := 0; i < len(objects); i++ { + j := rand.Intn(i + 1) + objects[i], objects[j] = objects[j], objects[i] + } + return objects +} + +var ( + injectOnly = &tag{} + injectPrivate = &tag{Private: true} + injectInline = &tag{Inline: true} +) + +type tag struct { + Name string + Inline bool + Private bool +} + +func parseTag(t string) (*tag, error) { + found, value, err := structtag.Extract("inject", t) + if err != nil { + return nil, err + } + if !found { + return nil, nil + } + if value == "" { + return injectOnly, nil + } + if value == "inline" { + return injectInline, nil + } + if value == "private" { + return injectPrivate, nil + } + return &tag{Name: value}, nil +} + +func isStructPtr(t reflect.Type) bool { + return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct +} + +func isNilOrZero(v reflect.Value, t reflect.Type) bool { + switch v.Kind() { + default: + return reflect.DeepEqual(v.Interface(), reflect.Zero(t).Interface()) + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } +} diff --git a/vendor/github.com/facebookgo/inject/license b/vendor/github.com/facebookgo/inject/license new file mode 100644 index 00000000000..953e8f7f10d --- /dev/null +++ b/vendor/github.com/facebookgo/inject/license @@ -0,0 +1,30 @@ +BSD License + +For inject software + +Copyright (c) 2015, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/facebookgo/inject/patents b/vendor/github.com/facebookgo/inject/patents new file mode 100644 index 00000000000..f33dc8011a8 --- /dev/null +++ b/vendor/github.com/facebookgo/inject/patents @@ -0,0 +1,33 @@ +Additional Grant of Patent Rights Version 2 + +"Software" means the inject software distributed by Facebook, Inc. + +Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software +("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable +(subject to the termination provision below) license under any Necessary +Claims, to make, have made, use, sell, offer to sell, import, and otherwise +transfer the Software. For avoidance of doubt, no license is granted under +Facebook’s rights in any patent claims that are infringed by (i) modifications +to the Software made by you or any third party or (ii) the Software in +combination with any software or other technology. + +The license granted hereunder will terminate, automatically and without notice, +if you (or any of your subsidiaries, corporate affiliates or agents) initiate +directly or indirectly, or take a direct financial interest in, any Patent +Assertion: (i) against Facebook or any of its subsidiaries or corporate +affiliates, (ii) against any party if such Patent Assertion arises in whole or +in part from any software, technology, product or service of Facebook or any of +its subsidiaries or corporate affiliates, or (iii) against any party relating +to the Software. Notwithstanding the foregoing, if Facebook or any of its +subsidiaries or corporate affiliates files a lawsuit alleging patent +infringement against you in the first instance, and you respond by filing a +patent infringement counterclaim in that lawsuit against that party that is +unrelated to the Software, the license granted hereunder will not terminate +under section (i) of this paragraph due to such counterclaim. + +A "Necessary Claim" is a claim of a patent owned by Facebook that is +necessarily infringed by the Software standing alone. + +A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, +or contributory infringement or inducement to infringe any patent, including a +cross-claim or counterclaim. diff --git a/vendor/github.com/facebookgo/structtag/license b/vendor/github.com/facebookgo/structtag/license new file mode 100644 index 00000000000..74487567632 --- /dev/null +++ b/vendor/github.com/facebookgo/structtag/license @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/facebookgo/structtag/structtag.go b/vendor/github.com/facebookgo/structtag/structtag.go new file mode 100644 index 00000000000..be9bc2293be --- /dev/null +++ b/vendor/github.com/facebookgo/structtag/structtag.go @@ -0,0 +1,61 @@ +// Package structtag provides parsing of the defacto struct tag style. +package structtag + +import ( + "errors" + "strconv" +) + +var errInvalidTag = errors.New("invalid tag") + +// Extract the quoted value for the given name returning it if it is found. The +// found boolean helps differentiate between the "empty and found" vs "empty +// and not found" nature of default empty strings. +func Extract(name, tag string) (found bool, value string, err error) { + for tag != "" { + // skip leading space + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // scan to colon. + // a space or a quote is a syntax error + i = 0 + for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { + i++ + } + if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + return false, "", errInvalidTag + } + foundName := string(tag[:i]) + tag = tag[i+1:] + + // scan quoted string to find value + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + return false, "", errInvalidTag + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if foundName == name { + value, err := strconv.Unquote(qvalue) + if err != nil { + return false, "", err + } + return true, value, nil + } + } + return false, "", nil +} diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 00000000000..835ba3e755c --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 00000000000..842ee80456d --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 00000000000..6b1f2891a5a --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,178 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +}