Merge branch 'master' into hotfix/979-no-rp
commit
d7f62fb781
|
@ -13,12 +13,14 @@
|
|||
1. [#1115](https://github.com/influxdata/chronograf/pull/1115): Fix Basepath issue where content would fail to render under certain circumstances
|
||||
1. [#1173](https://github.com/influxdata/chronograf/pull/1173): Fix saving email in Kapacitor alerts
|
||||
1. [#979](https://github.com/influxdata/chronograf/issues/979): Fix empty tags for non-default retention policies
|
||||
1. [#1179](https://github.com/influxdata/chronograf/pull/1179): Admin Databases Page will render a database without retention policies
|
||||
|
||||
### Features
|
||||
1. [#1112](https://github.com/influxdata/chronograf/pull/1112): Add ability to delete a dashboard
|
||||
1. [#1120](https://github.com/influxdata/chronograf/pull/1120): Allow users to update user passwords.
|
||||
1. [#1129](https://github.com/influxdata/chronograf/pull/1129): Allow InfluxDB and Kapacitor configuration via ENV vars or CLI options
|
||||
1. [#1130](https://github.com/influxdata/chronograf/pull/1130): Add loading spinner to Alert History page.
|
||||
1. [#1168](https://github.com/influxdata/chronograf/issue/1168): Expand support for --basepath on some load balancers
|
||||
|
||||
### UI Improvements
|
||||
1. [#1101](https://github.com/influxdata/chronograf/pull/1101): Compress InfluxQL responses with gzip
|
||||
|
|
|
@ -42,6 +42,19 @@ type Logger interface {
|
|||
Writer() *io.PipeWriter
|
||||
}
|
||||
|
||||
// Router is an abstracted Router based on the API provided by the
|
||||
// julienschmidt/httprouter package.
|
||||
type Router interface {
|
||||
http.Handler
|
||||
GET(string, http.HandlerFunc)
|
||||
PATCH(string, http.HandlerFunc)
|
||||
POST(string, http.HandlerFunc)
|
||||
DELETE(string, http.HandlerFunc)
|
||||
PUT(string, http.HandlerFunc)
|
||||
|
||||
Handler(string, string, http.Handler)
|
||||
}
|
||||
|
||||
// Assets returns a handler to serve the website.
|
||||
type Assets interface {
|
||||
Handler() http.Handler
|
||||
|
|
|
@ -20,7 +20,7 @@ type dbResponse struct {
|
|||
Duration string `json:"duration,omitempty"` // the duration (when creating a default retention policy)
|
||||
Replication int32 `json:"replication,omitempty"` // the replication factor (when creating a default retention policy)
|
||||
ShardDuration string `json:"shardDuration,omitempty"` // the shard duration (when creating a default retention policy)
|
||||
RPs []rpResponse `json:"retentionPolicies,omitempty"` // RPs are the retention policies for a database
|
||||
RPs []rpResponse `json:"retentionPolicies"` // RPs are the retention policies for a database
|
||||
Links dbLinks `json:"links"` // Links are URI locations related to the database
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
var _ chronograf.Router = &MountableRouter{}
|
||||
|
||||
// MountableRouter is an implementation of a chronograf.Router which supports
|
||||
// prefixing each route of a Delegated chronograf.Router with a prefix.
|
||||
type MountableRouter struct {
|
||||
Prefix string
|
||||
Delegate chronograf.Router
|
||||
}
|
||||
|
||||
// DELETE defines a route responding to a DELETE request that will be prefixed
|
||||
// with the configured route prefix
|
||||
func (mr *MountableRouter) DELETE(path string, handler http.HandlerFunc) {
|
||||
mr.Delegate.DELETE(mr.Prefix+path, handler)
|
||||
}
|
||||
|
||||
// GET defines a route responding to a GET request that will be prefixed
|
||||
// with the configured route prefix
|
||||
func (mr *MountableRouter) GET(path string, handler http.HandlerFunc) {
|
||||
mr.Delegate.GET(mr.Prefix+path, handler)
|
||||
}
|
||||
|
||||
// POST defines a route responding to a POST request that will be prefixed
|
||||
// with the configured route prefix
|
||||
func (mr *MountableRouter) POST(path string, handler http.HandlerFunc) {
|
||||
mr.Delegate.POST(mr.Prefix+path, handler)
|
||||
}
|
||||
|
||||
// PUT defines a route responding to a PUT request that will be prefixed
|
||||
// with the configured route prefix
|
||||
func (mr *MountableRouter) PUT(path string, handler http.HandlerFunc) {
|
||||
mr.Delegate.PUT(mr.Prefix+path, handler)
|
||||
}
|
||||
|
||||
// PATCH defines a route responding to a PATCH request that will be prefixed
|
||||
// with the configured route prefix
|
||||
func (mr *MountableRouter) PATCH(path string, handler http.HandlerFunc) {
|
||||
mr.Delegate.PATCH(mr.Prefix+path, handler)
|
||||
}
|
||||
|
||||
// Handler defines a prefixed route responding to a request type specified in
|
||||
// the method parameter
|
||||
func (mr *MountableRouter) Handler(method string, path string, handler http.Handler) {
|
||||
mr.Delegate.Handler(method, mr.Prefix+path, handler)
|
||||
}
|
||||
|
||||
// ServeHTTP is an implementation of http.Handler which delegates to the
|
||||
// configured Delegate's implementation of http.Handler
|
||||
func (mr *MountableRouter) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
mr.Delegate.ServeHTTP(rw, r)
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
package server_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bouk/httprouter"
|
||||
"github.com/influxdata/chronograf/server"
|
||||
)
|
||||
|
||||
func Test_MountableRouter_MountsRoutesUnderPrefix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mr := &server.MountableRouter{
|
||||
Prefix: "/chronograf",
|
||||
Delegate: httprouter.New(),
|
||||
}
|
||||
|
||||
expected := "Hello?! McFly?! Anybody in there?!"
|
||||
mr.GET("/biff", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(rw, expected)
|
||||
}))
|
||||
|
||||
ts := httptest.NewServer(mr)
|
||||
defer ts.Close()
|
||||
|
||||
resp, err := http.Get(ts.URL + "/chronograf/biff")
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error fetching from mounted router: err:", err)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error decoding response body: err:", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Expected 200 but received", resp.StatusCode)
|
||||
}
|
||||
|
||||
if string(body) != expected {
|
||||
t.Fatalf("Unexpected response body: Want: \"%s\". Got: \"%s\"", expected, string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MountableRouter_PrefixesPosts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mr := &server.MountableRouter{
|
||||
Prefix: "/chronograf",
|
||||
Delegate: httprouter.New(),
|
||||
}
|
||||
|
||||
expected := "Great Scott!"
|
||||
actual := make([]byte, len(expected))
|
||||
mr.POST("/doc", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
if _, err := io.ReadFull(r.Body, actual); err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
} else {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}))
|
||||
|
||||
ts := httptest.NewServer(mr)
|
||||
defer ts.Close()
|
||||
|
||||
resp, err := http.Post(ts.URL+"/chronograf/doc", "text/plain", strings.NewReader(expected))
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error posting to mounted router: err:", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Expected 200 but received", resp.StatusCode)
|
||||
}
|
||||
|
||||
if string(actual) != expected {
|
||||
t.Fatalf("Unexpected request body: Want: \"%s\". Got: \"%s\"", expected, string(actual))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MountableRouter_PrefixesPuts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mr := &server.MountableRouter{
|
||||
Prefix: "/chronograf",
|
||||
Delegate: httprouter.New(),
|
||||
}
|
||||
|
||||
expected := "Great Scott!"
|
||||
actual := make([]byte, len(expected))
|
||||
mr.PUT("/doc", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
if _, err := io.ReadFull(r.Body, actual); err != nil {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
} else {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}))
|
||||
|
||||
ts := httptest.NewServer(mr)
|
||||
defer ts.Close()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPut, ts.URL+"/chronograf/doc", strings.NewReader(expected))
|
||||
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
|
||||
req.Header.Set("Content-Length", fmt.Sprintf("%d", len(expected)))
|
||||
req.RequestURI = ""
|
||||
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error posting to mounted router: err:", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Expected 200 but received", resp.StatusCode)
|
||||
}
|
||||
|
||||
if string(actual) != expected {
|
||||
t.Fatalf("Unexpected request body: Want: \"%s\". Got: \"%s\"", expected, string(actual))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MountableRouter_PrefixesDeletes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mr := &server.MountableRouter{
|
||||
Prefix: "/chronograf",
|
||||
Delegate: httprouter.New(),
|
||||
}
|
||||
|
||||
mr.DELETE("/proto1985", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
|
||||
ts := httptest.NewServer(mr)
|
||||
defer ts.Close()
|
||||
|
||||
req := httptest.NewRequest(http.MethodDelete, ts.URL+"/chronograf/proto1985", nil)
|
||||
req.RequestURI = ""
|
||||
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error sending request to mounted router: err:", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
t.Fatal("Expected 204 but received", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MountableRouter_PrefixesPatches(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type Character struct {
|
||||
Name string
|
||||
Items []string
|
||||
}
|
||||
|
||||
mr := &server.MountableRouter{
|
||||
Prefix: "/chronograf",
|
||||
Delegate: httprouter.New(),
|
||||
}
|
||||
|
||||
biff := Character{"biff", []string{"sports almanac"}}
|
||||
mr.PATCH("/1955", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
c := Character{}
|
||||
err := json.NewDecoder(r.Body).Decode(&c)
|
||||
if err != nil {
|
||||
rw.WriteHeader(http.StatusBadRequest)
|
||||
} else {
|
||||
biff.Items = c.Items
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}))
|
||||
|
||||
ts := httptest.NewServer(mr)
|
||||
defer ts.Close()
|
||||
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
_ = json.NewEncoder(w).Encode(Character{"biff", []string{}})
|
||||
w.Close()
|
||||
}()
|
||||
|
||||
req := httptest.NewRequest(http.MethodPatch, ts.URL+"/chronograf/1955", r)
|
||||
req.RequestURI = ""
|
||||
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error sending request to mounted router: err:", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Expected 200 but received", resp.StatusCode)
|
||||
}
|
||||
|
||||
if len(biff.Items) != 0 {
|
||||
t.Fatal("Failed to alter history, biff still has the sports almanac")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MountableRouter_PrefixesHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mr := &server.MountableRouter{
|
||||
Prefix: "/chronograf",
|
||||
Delegate: httprouter.New(),
|
||||
}
|
||||
|
||||
mr.Handler(http.MethodGet, "/recklessAmountOfPower", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
rw.Write([]byte("1.21 Gigawatts!"))
|
||||
}))
|
||||
|
||||
ts := httptest.NewServer(mr)
|
||||
defer ts.Close()
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, ts.URL+"/chronograf/recklessAmountOfPower", nil)
|
||||
req.RequestURI = ""
|
||||
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error sending request to mounted router: err:", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Expected 200 but received", resp.StatusCode)
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ type MuxOpts struct {
|
|||
Logger chronograf.Logger
|
||||
Develop bool // Develop loads assets from filesystem instead of bindata
|
||||
Basepath string // URL path prefix under which all chronograf routes will be mounted
|
||||
PrefixRoutes bool // Mounts all backend routes under route specified by the Basepath
|
||||
UseAuth bool // UseAuth turns on Github OAuth and JWT
|
||||
TokenSecret string
|
||||
|
||||
|
@ -31,7 +32,7 @@ type MuxOpts struct {
|
|||
|
||||
// NewMux attaches all the route handlers; handler returned servers chronograf.
|
||||
func NewMux(opts MuxOpts, service Service) http.Handler {
|
||||
router := httprouter.New()
|
||||
hr := httprouter.New()
|
||||
|
||||
/* React Application */
|
||||
assets := Assets(AssetsOpts{
|
||||
|
@ -46,9 +47,23 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
compressed := gziphandler.GzipHandler(prefixedAssets)
|
||||
|
||||
// The react application handles all the routing if the server does not
|
||||
// know about the route. This means that we never have unknown
|
||||
// routes on the server.
|
||||
router.NotFound = compressed
|
||||
// know about the route. This means that we never have unknown routes on
|
||||
// the server.
|
||||
hr.NotFound = compressed
|
||||
|
||||
var router chronograf.Router = hr
|
||||
|
||||
// Set route prefix for all routes if basepath is present
|
||||
if opts.PrefixRoutes {
|
||||
router = &MountableRouter{
|
||||
Prefix: opts.Basepath,
|
||||
Delegate: hr,
|
||||
}
|
||||
|
||||
//The assets handler is always unaware of basepaths, so the
|
||||
// basepath needs to always be removed before sending requests to it
|
||||
hr.NotFound = http.StripPrefix(opts.Basepath, hr.NotFound)
|
||||
}
|
||||
|
||||
/* Documentation */
|
||||
router.GET("/swagger.json", Spec())
|
||||
|
@ -178,7 +193,7 @@ func NewMux(opts MuxOpts, service Service) http.Handler {
|
|||
|
||||
// AuthAPI adds the OAuth routes if auth is enabled.
|
||||
// TODO: this function is not great. Would be good if providers added their routes.
|
||||
func AuthAPI(opts MuxOpts, router *httprouter.Router) (http.Handler, AuthRoutes) {
|
||||
func AuthAPI(opts MuxOpts, router chronograf.Router) (http.Handler, AuthRoutes) {
|
||||
auth := oauth2.NewJWT(opts.TokenSecret)
|
||||
routes := AuthRoutes{}
|
||||
for _, pf := range opts.ProviderFuncs {
|
||||
|
|
|
@ -69,6 +69,7 @@ type Server struct {
|
|||
ReportingDisabled bool `short:"r" long:"reporting-disabled" description:"Disable reporting of usage stats (os,arch,version,cluster_id,uptime) once every 24hr" env:"REPORTING_DISABLED"`
|
||||
LogLevel string `short:"l" long:"log-level" value-name:"choice" choice:"debug" choice:"info" choice:"error" default:"info" description:"Set the logging level" env:"LOG_LEVEL"`
|
||||
Basepath string `short:"p" long:"basepath" description:"A URL path prefix under which all chronograf routes will be mounted" env:"BASE_PATH"`
|
||||
PrefixRoutes bool `long:"prefix-routes" description:"Force chronograf server to require that all requests to it are prefixed with the value set in --basepath" env:"PREFIX_ROUTES"`
|
||||
ShowVersion bool `short:"v" long:"version" description:"Show Chronograf version info"`
|
||||
BuildInfo BuildInfo
|
||||
Listener net.Listener
|
||||
|
@ -217,6 +218,8 @@ func (s *Server) Serve(ctx context.Context) error {
|
|||
Logger: logger,
|
||||
UseAuth: s.useAuth(),
|
||||
ProviderFuncs: providerFuncs,
|
||||
Basepath: basepath,
|
||||
PrefixRoutes: s.PrefixRoutes,
|
||||
}, service)
|
||||
|
||||
// Add chronograf's version header to all requests
|
||||
|
|
|
@ -2259,6 +2259,18 @@
|
|||
"duration": "3d",
|
||||
"replication": 3,
|
||||
"shardDuration": "3h",
|
||||
"retentionPolicies": [
|
||||
{
|
||||
"name": "weekly",
|
||||
"duration": "7d",
|
||||
"replication": 1,
|
||||
"shardDuration": "7d",
|
||||
"default": true,
|
||||
"links": {
|
||||
"self": "/chronograf/v1/ousrces/1/dbs/NOAA_water_database/rps/liquid"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "/chronograf/v1/sources/1/dbs/NOAA_water_database",
|
||||
"rps": "/chronograf/v1/sources/1/dbs/NOAA_water_database/rps"
|
||||
|
@ -2282,6 +2294,12 @@
|
|||
"type": "string",
|
||||
"description": "the interval spanned by each shard group"
|
||||
},
|
||||
"retentionPolicies": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/RetentionPolicy"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
Loading…
Reference in New Issue