Fix mux paths to be hardcoded; clarify server start; fix golint

pull/296/head
Chris Goller 2016-10-28 11:27:06 -05:00
parent d1359c09b3
commit d6a067427b
15 changed files with 191 additions and 168 deletions

View File

@ -8,9 +8,13 @@ import (
) )
const ( const (
Dir = "../ui/build" // Dir is prefix of the assets in the bindata
Default = "../ui/build/index.html" Dir = "../ui/build"
DebugDir = "ui/build" // Default is the default item to load if 404
Default = "../ui/build/index.html"
// DebugDir is the prefix of the assets in development mode
DebugDir = "ui/build"
// DebugDefault is the default item to load if 404
DebugDefault = "ui/build/index.html" DebugDefault = "ui/build/index.html"
) )

View File

@ -24,7 +24,7 @@ type exploration struct {
func newExploration(e *chronograf.Exploration) exploration { func newExploration(e *chronograf.Exploration) exploration {
rel := "self" rel := "self"
href := fmt.Sprintf("%s/%d/explorations/%d", httpAPIUsrs, e.UserID, e.ID) href := fmt.Sprintf("%s/%d/explorations/%d", "/chronograf/v1/users", e.UserID, e.ID)
return exploration{ return exploration{
Name: e.Name, Name: e.Name,
Data: e.Data, Data: e.Data,
@ -41,7 +41,8 @@ type explorations struct {
Explorations []exploration `json:"explorations"` Explorations []exploration `json:"explorations"`
} }
func (h *Store) Explorations(w http.ResponseWriter, r *http.Request) { // Explorations returns all explorations scoped by user id.
func (h *Service) Explorations(w http.ResponseWriter, r *http.Request) {
id, err := paramID("id", r) id, err := paramID("id", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -66,7 +67,8 @@ func (h *Store) Explorations(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, h.Logger) encodeJSON(w, http.StatusOK, res, h.Logger)
} }
func (h *Store) ExplorationsID(w http.ResponseWriter, r *http.Request) { // ExplorationsID retrieves exploration ID scoped under user.
func (h *Service) ExplorationsID(w http.ResponseWriter, r *http.Request) {
eID, err := paramID("eid", r) eID, err := paramID("eid", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -95,7 +97,8 @@ type patchExplorationRequest struct {
Name *string `json:"name,omitempty"` // Exploration name given by user. Name *string `json:"name,omitempty"` // Exploration name given by user.
} }
func (h *Store) UpdateExploration(w http.ResponseWriter, r *http.Request) { // UpdateExploration incrementally updates exploration
func (h *Service) UpdateExploration(w http.ResponseWriter, r *http.Request) {
id, err := paramID("eid", r) id, err := paramID("eid", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -149,7 +152,8 @@ type postExplorationRequest struct {
Name string `json:"name,omitempty"` // Exploration name given by user. Name string `json:"name,omitempty"` // Exploration name given by user.
} }
func (h *Store) NewExploration(w http.ResponseWriter, r *http.Request) { // NewExploration adds valid exploration scoped by user id.
func (h *Service) NewExploration(w http.ResponseWriter, r *http.Request) {
uID, err := paramID("id", r) uID, err := paramID("id", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -186,7 +190,8 @@ func (h *Store) NewExploration(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusCreated, res, h.Logger) encodeJSON(w, http.StatusCreated, res, h.Logger)
} }
func (h *Store) RemoveExploration(w http.ResponseWriter, r *http.Request) { // RemoveExploration deletes exploration from store.
func (h *Service) RemoveExploration(w http.ResponseWriter, r *http.Request) {
eID, err := paramID("eid", r) eID, err := paramID("eid", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())

View File

@ -15,7 +15,9 @@ import (
) )
const ( const (
DefaultCookieName = "session" // DefaultCookieName is the name of the stored cookie
DefaultCookieName = "session"
// DefaultCookieDuration is the length of time the cookie is valid
DefaultCookieDuration = time.Hour * 24 * 30 DefaultCookieDuration = time.Hour * 24 * 30
) )

View File

@ -47,7 +47,8 @@ type kapacitor struct {
Links kapaLinks `json:"links"` // Links are URI locations related to kapacitor Links kapaLinks `json:"links"` // Links are URI locations related to kapacitor
} }
func (h *Store) NewKapacitor(w http.ResponseWriter, r *http.Request) { // NewKapacitor adds valid kapacitor store store.
func (h *Service) NewKapacitor(w http.ResponseWriter, r *http.Request) {
srcID, err := paramID("id", r) srcID, err := paramID("id", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -91,6 +92,7 @@ func (h *Store) NewKapacitor(w http.ResponseWriter, r *http.Request) {
} }
func newKapacitor(srv chronograf.Server) kapacitor { func newKapacitor(srv chronograf.Server) kapacitor {
httpAPISrcs := "/chronograf/v1/sources"
return kapacitor{ return kapacitor{
ID: strconv.Itoa(srv.ID), ID: strconv.Itoa(srv.ID),
Name: srv.Name, Name: srv.Name,
@ -108,7 +110,8 @@ type kapacitors struct {
Kapacitors []kapacitor `json:"kapacitors"` Kapacitors []kapacitor `json:"kapacitors"`
} }
func (h *Store) Kapacitors(w http.ResponseWriter, r *http.Request) { // Kapacitors retrieves all kapacitors from store.
func (h *Service) Kapacitors(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
mrSrvs, err := h.ServersStore.All(ctx) mrSrvs, err := h.ServersStore.All(ctx)
if err != nil { if err != nil {
@ -128,7 +131,8 @@ func (h *Store) Kapacitors(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, h.Logger) encodeJSON(w, http.StatusOK, res, h.Logger)
} }
func (h *Store) KapacitorsID(w http.ResponseWriter, r *http.Request) { // KapacitorsID retrieves a kapacitor with ID from store.
func (h *Service) KapacitorsID(w http.ResponseWriter, r *http.Request) {
id, err := paramID("kid", r) id, err := paramID("kid", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -152,7 +156,8 @@ func (h *Store) KapacitorsID(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, h.Logger) encodeJSON(w, http.StatusOK, res, h.Logger)
} }
func (h *Store) RemoveKapacitor(w http.ResponseWriter, r *http.Request) { // RemoveKapacitor deletes kapacitor from store.
func (h *Service) RemoveKapacitor(w http.ResponseWriter, r *http.Request) {
id, err := paramID("kid", r) id, err := paramID("kid", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -199,7 +204,8 @@ func (p *patchKapacitorRequest) Valid() error {
return nil return nil
} }
func (h *Store) UpdateKapacitor(w http.ResponseWriter, r *http.Request) { // UpdateKapacitor incrementally updates a kapacitor definition in the store
func (h *Service) UpdateKapacitor(w http.ResponseWriter, r *http.Request) {
id, err := paramID("kid", r) id, err := paramID("kid", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())

View File

@ -15,6 +15,7 @@ type layoutResponse struct {
} }
func newLayoutResponse(layout chronograf.Layout) layoutResponse { func newLayoutResponse(layout chronograf.Layout) layoutResponse {
httpAPILayouts := "/chronograf/v1/layouts"
href := fmt.Sprintf("%s/%s", httpAPILayouts, layout.ID) href := fmt.Sprintf("%s/%s", httpAPILayouts, layout.ID)
rel := "self" rel := "self"
@ -27,7 +28,8 @@ func newLayoutResponse(layout chronograf.Layout) layoutResponse {
} }
} }
func (h *Store) NewLayout(w http.ResponseWriter, r *http.Request) { // NewLayout adds a valid layout to store.
func (h *Service) NewLayout(w http.ResponseWriter, r *http.Request) {
var layout chronograf.Layout var layout chronograf.Layout
if err := json.NewDecoder(r.Body).Decode(&layout); err != nil { if err := json.NewDecoder(r.Body).Decode(&layout); err != nil {
invalidJSON(w) invalidJSON(w)
@ -54,7 +56,8 @@ type getLayoutsResponse struct {
Layouts []layoutResponse `json:"layouts"` Layouts []layoutResponse `json:"layouts"`
} }
func (h *Store) Layouts(w http.ResponseWriter, r *http.Request) { // Layouts retrieves all layouts from store
func (h *Service) Layouts(w http.ResponseWriter, r *http.Request) {
// Construct a filter sieve for both applications and measurements // Construct a filter sieve for both applications and measurements
filtered := map[string]bool{} filtered := map[string]bool{}
for _, a := range r.URL.Query()["app"] { for _, a := range r.URL.Query()["app"] {
@ -93,7 +96,8 @@ func (h *Store) Layouts(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, h.Logger) encodeJSON(w, http.StatusOK, res, h.Logger)
} }
func (h *Store) LayoutsID(w http.ResponseWriter, r *http.Request) { // LayoutsID retrieves layout with ID from store
func (h *Service) LayoutsID(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
id := httprouter.GetParamFromContext(ctx, "id") id := httprouter.GetParamFromContext(ctx, "id")
@ -107,7 +111,8 @@ func (h *Store) LayoutsID(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, h.Logger) encodeJSON(w, http.StatusOK, res, h.Logger)
} }
func (h *Store) RemoveLayout(w http.ResponseWriter, r *http.Request) { // RemoveLayout deletes layout from store.
func (h *Service) RemoveLayout(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
id := httprouter.GetParamFromContext(ctx, "id") id := httprouter.GetParamFromContext(ctx, "id")
@ -123,7 +128,8 @@ func (h *Store) RemoveLayout(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
func (h *Store) UpdateLayout(w http.ResponseWriter, r *http.Request) { // UpdateLayout replaces the layout of ID with new valid layout.
func (h *Service) UpdateLayout(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
id := httprouter.GetParamFromContext(ctx, "id") id := httprouter.GetParamFromContext(ctx, "id")
@ -155,6 +161,7 @@ func (h *Store) UpdateLayout(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, h.Logger) encodeJSON(w, http.StatusOK, res, h.Logger)
} }
// ValidLayoutRequest checks if the layout has valid application, measurement and cells.
func ValidLayoutRequest(l chronograf.Layout) error { func ValidLayoutRequest(l chronograf.Layout) error {
if l.Application == "" || l.Measurement == "" || len(l.Cells) == 0 { if l.Application == "" || l.Measurement == "" || len(l.Cells) == 0 {
return fmt.Errorf("app, measurement, and cells required") return fmt.Errorf("app, measurement, and cells required")

View File

@ -6,6 +6,7 @@ import (
"github.com/influxdata/chronograf" "github.com/influxdata/chronograf"
) )
// Logger is middleware that logs the request
func Logger(logger chronograf.Logger, next http.Handler) http.Handler { func Logger(logger chronograf.Logger, next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) { fn := func(w http.ResponseWriter, r *http.Request) {
logger. logger.

View File

@ -11,7 +11,8 @@ type mapping struct {
Name string `json:"name"` // The application name which will be assigned to the corresponding measurement Name string `json:"name"` // The application name which will be assigned to the corresponding measurement
} }
func (h *Store) GetMappings(w http.ResponseWriter, r *http.Request) { // GetMappings returns the known mappings of measurements to applications
func (h *Service) GetMappings(w http.ResponseWriter, r *http.Request) {
cpu := "cpu" cpu := "cpu"
system := "System" system := "System"
mp := getMappingsResponse{ mp := getMappingsResponse{

View File

@ -17,40 +17,18 @@ const (
JSONType = "application/json" JSONType = "application/json"
) )
var ( // MuxOpts are the options for the router. Mostly related to auth.
httpAPIRoot = "/chronograf/v1/"
httpAPILayouts = "/chronograf/v1/layouts"
httpAPILayoutsID = "/chronograf/v1/layouts/:id"
httpAPIMappings = "/chronograf/v1/mappings"
httpAPISrcs = "/chronograf/v1/sources"
httpAPISrcsID = "/chronograf/v1/sources/:id"
httpAPISrcsIDProxy = "/chronograf/v1/sources/:id/proxy"
httpAPISrcsIDKapas = "/chronograf/v1/sources/:id/kapacitors"
httpAPISrcsIDKapasID = "/chronograf/v1/sources/:id/kapacitors/:kid"
httpAPISrcsIDKapasIDProxy = "/chronograf/v1/sources/:id/kapacitors/:kid/proxy"
httpAPIUsrs = "/chronograf/v1/users"
httpAPIUsrsID = "/chronograf/v1/users/:id"
httpAPIUsrsIDExps = "/chronograf/v1/users/:id/explorations"
httpAPIUsrsIDExpsID = "/chronograf/v1/users/:id/explorations/:eid"
httpOAuth = "/oauth"
httpOAuthLogout = "/oauth/logout"
httpOAuthGHCallback = "/oauth/github/callback"
httpSwagger = "/swagger.json"
httpDocs = "/docs"
)
type MuxOpts struct { type MuxOpts struct {
Develop bool
UseAuth bool
TokenSecret string
GithubClientID string
GithubClientSecret string
Logger chronograf.Logger Logger chronograf.Logger
Develop bool // Develop loads assets from filesystem instead of bindata
UseAuth bool // UseAuth turns on Github OAuth and JWT
TokenSecret string // TokenSecret is the JWT secret
GithubClientID string // GithubClientID is the GH OAuth id
GithubClientSecret string // GithubClientSecret is the GH OAuth secret
} }
func NewMux(opts MuxOpts, store Store, proxy InfluxProxy) http.Handler { // NewMux attaches all the route handlers; handler returned servers chronograf.
func NewMux(opts MuxOpts, service Service) http.Handler {
router := httprouter.New() router := httprouter.New()
/* React Application */ /* React Application */
@ -64,65 +42,65 @@ func NewMux(opts MuxOpts, store Store, proxy InfluxProxy) http.Handler {
router.NotFound = assets router.NotFound = assets
/* Documentation */ /* Documentation */
router.GET(httpSwagger, Spec()) router.GET("/swagger.json", Spec())
router.GET(httpDocs, Redoc(httpSwagger)) router.GET("/docs", Redoc("/swagger.json"))
/* API */ /* API */
// Root Routes returns all top-level routes in the API // Root Routes returns all top-level routes in the API
router.GET(httpAPIRoot, AllRoutes(opts.Logger)) router.GET("/chronograf/v1/", AllRoutes(opts.Logger))
// Sources // Sources
router.GET(httpAPISrcs, store.Sources) router.GET("/chronograf/v1/sources", service.Sources)
router.POST(httpAPISrcs, store.NewSource) router.POST("/chronograf/v1/sources", service.NewSource)
router.GET(httpAPISrcsID, store.SourcesID) router.GET("/chronograf/v1/sources/:id", service.SourcesID)
router.PATCH(httpAPISrcsID, store.UpdateSource) router.PATCH("/chronograf/v1/sources/:id", service.UpdateSource)
router.DELETE(httpAPISrcsID, store.RemoveSource) router.DELETE("/chronograf/v1/sources/:id", service.RemoveSource)
// Source Proxy // Source Proxy
router.POST(httpAPISrcsIDProxy, proxy.Proxy) router.POST("/chronograf/v1/sources/:id/proxy", service.Proxy)
// Kapacitor // Kapacitor
router.GET(httpAPISrcsIDKapas, store.Kapacitors) router.GET("/chronograf/v1/sources/:id/kapacitors", service.Kapacitors)
router.POST(httpAPISrcsIDKapas, store.NewKapacitor) router.POST("/chronograf/v1/sources/:id/kapacitors", service.NewKapacitor)
router.GET(httpAPISrcsIDKapasID, store.KapacitorsID) router.GET("/chronograf/v1/sources/:id/kapacitors/:kid", service.KapacitorsID)
router.PATCH(httpAPISrcsIDKapasID, store.UpdateKapacitor) router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid", service.UpdateKapacitor)
router.DELETE(httpAPISrcsIDKapasID, store.RemoveKapacitor) router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid", service.RemoveKapacitor)
// Kapacitor Proxy // Kapacitor Proxy
router.GET(httpAPISrcsIDKapasIDProxy, proxy.KapacitorProxyGet) router.GET("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", service.KapacitorProxyGet)
router.POST(httpAPISrcsIDKapasIDProxy, proxy.KapacitorProxyPost) router.POST("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", service.KapacitorProxyPost)
router.PATCH(httpAPISrcsIDKapasIDProxy, proxy.KapacitorProxyPatch) router.PATCH("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", service.KapacitorProxyPatch)
router.DELETE(httpAPISrcsIDKapasIDProxy, proxy.KapacitorProxyDelete) router.DELETE("/chronograf/v1/sources/:id/kapacitors/:kid/proxy", service.KapacitorProxyDelete)
// Mappings // Mappings
router.GET(httpAPIMappings, store.GetMappings) router.GET("/chronograf/v1/mappings", service.GetMappings)
// Layouts // Layouts
router.GET(httpAPILayouts, store.Layouts) router.GET("/chronograf/v1/layouts", service.Layouts)
router.POST(httpAPILayouts, store.NewLayout) router.POST("/chronograf/v1/layouts", service.NewLayout)
router.GET(httpAPILayoutsID, store.LayoutsID) router.GET("/chronograf/v1/layouts/:id", service.LayoutsID)
router.PUT(httpAPILayoutsID, store.UpdateLayout) router.PUT("/chronograf/v1/layouts/:id", service.UpdateLayout)
router.DELETE(httpAPILayoutsID, store.RemoveLayout) router.DELETE("/chronograf/v1/layouts/:id", service.RemoveLayout)
// Users // Users
/* /*
router.GET(httpAPIUsrs, Users) router.GET("/chronograf/v1/users", Users)
router.POST(httpAPIUsrs, NewUser) router.POST("/chronograf/v1/users", NewUser)
router.GET(httpAPIUsrsID, UsersID) router.GET("/chronograf/v1/users/:id", UsersID)
router.PATCH(httpAPIUsrsID, UpdateUser) router.PATCH("/chronograf/v1/users/:id", UpdateUser)
router.DELETE(httpAPIUsrsID, RemoveUser) router.DELETE("/chronograf/v1/users/:id", RemoveUser)
*/ */
// Explorations // Explorations
router.GET(httpAPIUsrsIDExps, store.Explorations) router.GET("/chronograf/v1/users/:id/explorations", service.Explorations)
router.POST(httpAPIUsrsIDExps, store.NewExploration) router.POST("/chronograf/v1/users/:id/explorations", service.NewExploration)
router.GET(httpAPIUsrsIDExpsID, store.ExplorationsID) router.GET("/chronograf/v1/users/:id/explorations/:eid", service.ExplorationsID)
router.PATCH(httpAPIUsrsIDExpsID, store.UpdateExploration) router.PATCH("/chronograf/v1/users/:id/explorations/:eid", service.UpdateExploration)
router.DELETE(httpAPIUsrsIDExpsID, store.RemoveExploration) router.DELETE("/chronograf/v1/users/:id/explorations/:eid", service.RemoveExploration)
/* Authentication */ /* Authentication */
if opts.UseAuth { if opts.UseAuth {
@ -132,6 +110,7 @@ func NewMux(opts MuxOpts, store Store, proxy InfluxProxy) http.Handler {
return Logger(opts.Logger, router) return Logger(opts.Logger, router)
} }
// AuthAPI adds the OAuth routes if auth is enabled.
func AuthAPI(opts MuxOpts, router *httprouter.Router) http.Handler { func AuthAPI(opts MuxOpts, router *httprouter.Router) http.Handler {
auth := jwt.NewJWT(opts.TokenSecret) auth := jwt.NewJWT(opts.TokenSecret)
@ -146,19 +125,18 @@ func AuthAPI(opts MuxOpts, router *httprouter.Router) http.Handler {
opts.Logger, opts.Logger,
) )
router.GET(httpOAuth, gh.Login()) router.GET("/oauth", gh.Login())
router.GET(httpOAuthLogout, gh.Logout()) router.GET("/oauth/logout", gh.Logout())
router.GET(httpOAuthGHCallback, gh.Callback()) router.GET("/oauth/github/callback", gh.Callback())
tokenMiddleware := AuthorizedToken(&auth, &CookieExtractor{Name: "session"}, opts.Logger, router) tokenMiddleware := AuthorizedToken(&auth, &CookieExtractor{Name: "session"}, opts.Logger, router)
// Wrap the API with token validation middleware. // Wrap the API with token validation middleware.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, httpAPIRoot) { if strings.HasPrefix(r.URL.Path, "/chronograf/v1/") {
tokenMiddleware.ServeHTTP(w, r) tokenMiddleware.ServeHTTP(w, r)
return return
} else {
router.ServeHTTP(w, r)
} }
router.ServeHTTP(w, r)
}) })
} }
@ -170,6 +148,7 @@ func encodeJSON(w http.ResponseWriter, status int, v interface{}, logger chronog
} }
} }
// Error writes an JSON message
func Error(w http.ResponseWriter, code int, msg string) { func Error(w http.ResponseWriter, code int, msg string) {
e := struct { e := struct {
Code int `json:"code"` Code int `json:"code"`

View File

@ -10,13 +10,7 @@ import (
"github.com/influxdata/chronograf" "github.com/influxdata/chronograf"
) )
type InfluxProxy struct { // ValidProxyRequest checks if queries specify a command.
Srcs chronograf.SourcesStore
ServersStore chronograf.ServersStore
TimeSeries chronograf.TimeSeries
Logger chronograf.Logger
}
func ValidProxyRequest(p chronograf.Query) error { func ValidProxyRequest(p chronograf.Query) error {
if p.Command == "" { if p.Command == "" {
return fmt.Errorf("query field required") return fmt.Errorf("query field required")
@ -28,7 +22,8 @@ type postProxyResponse struct {
Results interface{} `json:"results"` // results from influx Results interface{} `json:"results"` // results from influx
} }
func (h *InfluxProxy) Proxy(w http.ResponseWriter, r *http.Request) { // Proxy proxies requests to infludb.
func (h *Service) Proxy(w http.ResponseWriter, r *http.Request) {
id, err := paramID("id", r) id, err := paramID("id", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -46,7 +41,7 @@ func (h *InfluxProxy) Proxy(w http.ResponseWriter, r *http.Request) {
} }
ctx := r.Context() ctx := r.Context()
src, err := h.Srcs.Get(ctx, id) src, err := h.SourcesStore.Get(ctx, id)
if err != nil { if err != nil {
notFound(w, id) notFound(w, id)
return return
@ -76,7 +71,8 @@ func (h *InfluxProxy) Proxy(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, h.Logger) encodeJSON(w, http.StatusOK, res, h.Logger)
} }
func (h *InfluxProxy) KapacitorProxy(w http.ResponseWriter, r *http.Request) { // KapacitorProxy proxies requests to kapacitor using the path query parameter.
func (h *Service) KapacitorProxy(w http.ResponseWriter, r *http.Request) {
srcID, err := paramID("id", r) srcID, err := paramID("id", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -123,18 +119,22 @@ func (h *InfluxProxy) KapacitorProxy(w http.ResponseWriter, r *http.Request) {
proxy.ServeHTTP(w, r) proxy.ServeHTTP(w, r)
} }
func (h *InfluxProxy) KapacitorProxyPost(w http.ResponseWriter, r *http.Request) { // KapacitorProxyPost proxies POST to kapacitor
func (h *Service) KapacitorProxyPost(w http.ResponseWriter, r *http.Request) {
h.KapacitorProxy(w, r) h.KapacitorProxy(w, r)
} }
func (h *InfluxProxy) KapacitorProxyPatch(w http.ResponseWriter, r *http.Request) { // KapacitorProxyPatch proxies PATCH to kapacitor
func (h *Service) KapacitorProxyPatch(w http.ResponseWriter, r *http.Request) {
h.KapacitorProxy(w, r) h.KapacitorProxy(w, r)
} }
func (h *InfluxProxy) KapacitorProxyGet(w http.ResponseWriter, r *http.Request) { // KapacitorProxyGet proxies GET to kapacitor
func (h *Service) KapacitorProxyGet(w http.ResponseWriter, r *http.Request) {
h.KapacitorProxy(w, r) h.KapacitorProxy(w, r)
} }
func (h *InfluxProxy) KapacitorProxyDelete(w http.ResponseWriter, r *http.Request) { // KapacitorProxyDelete proxies DELETE to kapacitor
func (h *Service) KapacitorProxyDelete(w http.ResponseWriter, r *http.Request) {
h.KapacitorProxy(w, r) h.KapacitorProxy(w, r)
} }

View File

@ -28,6 +28,7 @@ const index = `<!DOCTYPE html>
</html> </html>
` `
// Redoc servers the swagger JSON using the redoc package.
func Redoc(swagger string) http.HandlerFunc { func Redoc(swagger string) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "text/html; charset=utf-8") rw.Header().Set("Content-Type", "text/html; charset=utf-8")

View File

@ -13,12 +13,13 @@ type getRoutesResponse struct {
Users string `json:"users"` // Location of the users endpoint Users string `json:"users"` // Location of the users endpoint
} }
// AllRoutes returns all top level routes within chronograf
func AllRoutes(logger chronograf.Logger) http.HandlerFunc { func AllRoutes(logger chronograf.Logger) http.HandlerFunc {
routes := getRoutesResponse{ routes := getRoutesResponse{
Sources: httpAPISrcs, Sources: "/chronograf/v1/sources",
Layouts: httpAPILayouts, Layouts: "/chronograf/v1/layouts",
Users: httpAPIUsrs, Users: "/chronograf/v1/users",
Mappings: httpAPIMappings, Mappings: "/chronograf/v1/mappings",
} }
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View File

@ -1,11 +1,9 @@
package server package server
import ( import (
"log"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"sync"
"time" "time"
"github.com/influxdata/chronograf" "github.com/influxdata/chronograf"
@ -19,7 +17,7 @@ import (
"github.com/tylerb/graceful" "github.com/tylerb/graceful"
) )
var logger chronograf.Logger = clog.New() var logger = clog.New()
// Server for the chronograf API // Server for the chronograf API
type Server struct { type Server struct {
@ -38,73 +36,82 @@ type Server struct {
GithubClientID string `short:"i" long:"github-client-id" description:"Github Client ID for OAuth 2 support" env:"GH_CLIENT_ID"` GithubClientID string `short:"i" long:"github-client-id" description:"Github Client ID for OAuth 2 support" env:"GH_CLIENT_ID"`
GithubClientSecret string `short:"s" long:"github-client-secret" description:"Github Client Secret for OAuth 2 support" env:"GH_CLIENT_SECRET"` GithubClientSecret string `short:"s" long:"github-client-secret" description:"Github Client Secret for OAuth 2 support" env:"GH_CLIENT_SECRET"`
httpServerL net.Listener Listener net.Listener
handler http.Handler handler http.Handler
} }
func (s *Server) useAuth() bool {
return s.TokenSecret != "" && s.GithubClientID != "" && s.GithubClientSecret != ""
}
// Serve starts and runs the chronograf server
func (s *Server) Serve() error { func (s *Server) Serve() error {
c := bolt.NewClient() service := openService(s.BoltPath, s.CannedPath)
c.Path = s.BoltPath
if err := c.Open(); err != nil {
logger.WithField("component", "boltstore").Panic("Unable to open boltdb; is there a mrfusion already running?", err)
panic(err)
}
apps := canned.NewApps(s.CannedPath, &uuid.V4{})
// allLayouts acts as a front-end to both the bolt layouts and the filesystem layouts.
allLayouts := &layouts.MultiLayoutStore{
Stores: []chronograf.LayoutStore{
c.LayoutStore,
apps,
},
}
h := Store{
ExplorationStore: c.ExplorationStore,
SourcesStore: c.SourcesStore,
ServersStore: c.ServersStore,
LayoutStore: allLayouts,
}
p := InfluxProxy{
Srcs: c.SourcesStore,
TimeSeries: &influx.Client{},
ServersStore: c.ServersStore,
}
useAuth := s.TokenSecret != "" && s.GithubClientID != "" && s.GithubClientSecret != ""
s.handler = NewMux(MuxOpts{ s.handler = NewMux(MuxOpts{
Develop: s.Develop, Develop: s.Develop,
TokenSecret: s.TokenSecret, TokenSecret: s.TokenSecret,
GithubClientID: s.GithubClientID, GithubClientID: s.GithubClientID,
GithubClientSecret: s.GithubClientSecret, GithubClientSecret: s.GithubClientSecret,
Logger: logger, Logger: logger,
UseAuth: useAuth, UseAuth: s.useAuth(),
}, h, p) }, service)
listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port))) var err error
s.Listener, err = net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port)))
if err != nil { if err != nil {
logger.
WithField("component", "server").
Error(err)
return err return err
} }
s.httpServerL = listener
var wg sync.WaitGroup
httpServer := &graceful.Server{Server: new(http.Server)} httpServer := &graceful.Server{Server: new(http.Server)}
httpServer.SetKeepAlivesEnabled(true) httpServer.SetKeepAlivesEnabled(true)
httpServer.TCPKeepAlive = 3 * time.Minute httpServer.TCPKeepAlive = 1 * time.Minute
httpServer.Handler = s.handler httpServer.Handler = s.handler
wg.Add(1) logger.
log.Printf("Serving chronograf at http://%s", s.httpServerL.Addr()) WithField("component", "server").
go func(l net.Listener) { Info("Serving chronograf at http://%s", s.Listener.Addr())
defer wg.Done()
if err := httpServer.Serve(l); err != nil { if err := httpServer.Serve(s.Listener); err != nil {
log.Fatalf("%v", err) logger.
} WithField("component", "server").
log.Printf("Stopped serving chronograf at http://%s", l.Addr()) Error(err)
}(s.httpServerL) return err
}
logger.
WithField("component", "server").
Info("Stopped serving chronograf at http://%s", s.Listener.Addr())
wg.Wait()
return nil return nil
} }
func openService(boltPath, cannedPath string) Service {
db := bolt.NewClient()
db.Path = boltPath
if err := db.Open(); err != nil {
logger.
WithField("component", "boltstore").
Panic("Unable to open boltdb; is there a mrfusion already running?", err)
panic(err)
}
apps := canned.NewApps(cannedPath, &uuid.V4{})
// Acts as a front-end to both the bolt layouts and the filesystem layouts.
layouts := &layouts.MultiLayoutStore{
Stores: []chronograf.LayoutStore{
db.LayoutStore,
apps,
},
}
return Service{
ExplorationStore: db.ExplorationStore,
SourcesStore: db.SourcesStore,
ServersStore: db.ServersStore,
TimeSeries: &influx.Client{},
LayoutStore: layouts,
}
}

View File

@ -2,11 +2,12 @@ package server
import "github.com/influxdata/chronograf" import "github.com/influxdata/chronograf"
// Store handles REST calls to the persistence // Service handles REST calls to the persistence
type Store struct { type Service struct {
ExplorationStore chronograf.ExplorationStore ExplorationStore chronograf.ExplorationStore
SourcesStore chronograf.SourcesStore SourcesStore chronograf.SourcesStore
ServersStore chronograf.ServersStore ServersStore chronograf.ServersStore
LayoutStore chronograf.LayoutStore LayoutStore chronograf.LayoutStore
TimeSeries chronograf.TimeSeries
Logger chronograf.Logger Logger chronograf.Logger
} }

View File

@ -21,6 +21,7 @@ type sourceResponse struct {
} }
func newSourceResponse(src chronograf.Source) sourceResponse { func newSourceResponse(src chronograf.Source) sourceResponse {
httpAPISrcs := "/chronograf/v1/sources"
return sourceResponse{ return sourceResponse{
Source: src, Source: src,
Links: sourceLinks{ Links: sourceLinks{
@ -31,7 +32,8 @@ func newSourceResponse(src chronograf.Source) sourceResponse {
} }
} }
func (h *Store) NewSource(w http.ResponseWriter, r *http.Request) { // NewSource adds a new valid source to the store
func (h *Service) NewSource(w http.ResponseWriter, r *http.Request) {
var src chronograf.Source var src chronograf.Source
if err := json.NewDecoder(r.Body).Decode(&src); err != nil { if err := json.NewDecoder(r.Body).Decode(&src); err != nil {
invalidJSON(w) invalidJSON(w)
@ -58,7 +60,8 @@ type getSourcesResponse struct {
Sources []sourceResponse `json:"sources"` Sources []sourceResponse `json:"sources"`
} }
func (h *Store) Sources(w http.ResponseWriter, r *http.Request) { // Sources returns all sources from the store.
func (h *Service) Sources(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
srcs, err := h.SourcesStore.All(ctx) srcs, err := h.SourcesStore.All(ctx)
if err != nil { if err != nil {
@ -77,7 +80,8 @@ func (h *Store) Sources(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, h.Logger) encodeJSON(w, http.StatusOK, res, h.Logger)
} }
func (h *Store) SourcesID(w http.ResponseWriter, r *http.Request) { // SourcesID retrieves a source from the store
func (h *Service) SourcesID(w http.ResponseWriter, r *http.Request) {
id, err := paramID("id", r) id, err := paramID("id", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -95,7 +99,8 @@ func (h *Store) SourcesID(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, res, h.Logger) encodeJSON(w, http.StatusOK, res, h.Logger)
} }
func (h *Store) RemoveSource(w http.ResponseWriter, r *http.Request) { // RemoveSource deletes the source from the store
func (h *Service) RemoveSource(w http.ResponseWriter, r *http.Request) {
id, err := paramID("id", r) id, err := paramID("id", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -112,7 +117,8 @@ func (h *Store) RemoveSource(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} }
func (h *Store) UpdateSource(w http.ResponseWriter, r *http.Request) { // UpdateSource handles incremental updates of a data source
func (h *Service) UpdateSource(w http.ResponseWriter, r *http.Request) {
id, err := paramID("id", r) id, err := paramID("id", r)
if err != nil { if err != nil {
Error(w, http.StatusUnprocessableEntity, err.Error()) Error(w, http.StatusUnprocessableEntity, err.Error())
@ -162,6 +168,7 @@ func (h *Store) UpdateSource(w http.ResponseWriter, r *http.Request) {
encodeJSON(w, http.StatusOK, newSourceResponse(src), h.Logger) encodeJSON(w, http.StatusOK, newSourceResponse(src), h.Logger)
} }
// ValidSourceRequest checks if name, url and type are valid
func ValidSourceRequest(s chronograf.Source) error { func ValidSourceRequest(s chronograf.Source) error {
// Name and URL areq required // Name and URL areq required
if s.Name == "" || s.URL == "" { if s.Name == "" || s.URL == "" {

View File

@ -4,6 +4,7 @@ package server
import "net/http" import "net/http"
// Spec servers the swagger.json file from bindata
func Spec() http.HandlerFunc { func Spec() http.HandlerFunc {
swagger, err := Asset("swagger.json") swagger, err := Asset("swagger.json")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {