2018-09-04 21:08:00 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2018-09-14 04:01:07 +00:00
|
|
|
"time"
|
2018-09-04 21:08:00 +00:00
|
|
|
"unicode/utf8"
|
|
|
|
|
2018-09-06 18:09:52 +00:00
|
|
|
"github.com/influxdata/flux"
|
2018-09-14 04:01:07 +00:00
|
|
|
"github.com/influxdata/flux/ast"
|
2018-09-06 18:09:52 +00:00
|
|
|
"github.com/influxdata/flux/csv"
|
2018-09-11 22:56:51 +00:00
|
|
|
"github.com/influxdata/flux/lang"
|
2018-09-14 04:01:07 +00:00
|
|
|
"github.com/influxdata/flux/semantic"
|
|
|
|
"github.com/influxdata/flux/values"
|
2018-09-04 21:08:00 +00:00
|
|
|
"github.com/influxdata/platform"
|
|
|
|
"github.com/influxdata/platform/kit/errors"
|
2018-09-11 22:56:51 +00:00
|
|
|
"github.com/influxdata/platform/query"
|
2018-09-04 21:08:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// QueryRequest is a flux query request.
|
|
|
|
type QueryRequest struct {
|
2018-09-06 18:09:52 +00:00
|
|
|
Spec *flux.Spec `json:"spec,omitempty"`
|
2018-09-14 04:01:07 +00:00
|
|
|
AST *ast.Program `json:"ast,omitempty"`
|
2018-09-04 21:08:00 +00:00
|
|
|
Query string `json:"query"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Dialect QueryDialect `json:"dialect"`
|
|
|
|
|
|
|
|
org *platform.Organization
|
|
|
|
}
|
|
|
|
|
|
|
|
// QueryDialect is the formatting options for the query response.
|
|
|
|
type QueryDialect struct {
|
|
|
|
Header *bool `json:"header"`
|
|
|
|
Delimiter string `json:"delimiter"`
|
|
|
|
CommentPrefix string `json:"commentPrefix"`
|
|
|
|
DateTimeFormat string `json:"dateTimeFormat"`
|
|
|
|
Annotations []string `json:"annotations"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// WithDefaults adds default values to the request.
|
|
|
|
func (r QueryRequest) WithDefaults() QueryRequest {
|
|
|
|
if r.Type == "" {
|
|
|
|
r.Type = "flux"
|
|
|
|
}
|
|
|
|
if r.Dialect.Delimiter == "" {
|
|
|
|
r.Dialect.Delimiter = ","
|
|
|
|
}
|
|
|
|
if r.Dialect.DateTimeFormat == "" {
|
|
|
|
r.Dialect.DateTimeFormat = "RFC3339"
|
|
|
|
}
|
|
|
|
if r.Dialect.Header == nil {
|
|
|
|
header := true
|
|
|
|
r.Dialect.Header = &header
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate checks the query request and returns an error if the request is invalid.
|
|
|
|
func (r QueryRequest) Validate() error {
|
2018-09-14 04:01:07 +00:00
|
|
|
if r.Query == "" && r.Spec == nil && r.AST == nil {
|
|
|
|
return errors.New(`request body requires either query, spec, or AST`)
|
2018-09-04 21:08:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if r.Type != "flux" {
|
|
|
|
return fmt.Errorf(`unknown query type: %s`, r.Type)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(r.Dialect.CommentPrefix) > 1 {
|
|
|
|
return fmt.Errorf("invalid dialect comment prefix: must be length 0 or 1")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(r.Dialect.Delimiter) != 1 {
|
|
|
|
return fmt.Errorf("invalid dialect delimeter: must be length 1")
|
|
|
|
}
|
|
|
|
|
|
|
|
rune, size := utf8.DecodeRuneInString(r.Dialect.Delimiter)
|
|
|
|
if rune == utf8.RuneError && size == 1 {
|
|
|
|
return fmt.Errorf("invalid dialect delimeter character")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, a := range r.Dialect.Annotations {
|
|
|
|
switch a {
|
|
|
|
case "group", "datatype", "default":
|
|
|
|
default:
|
|
|
|
return fmt.Errorf(`unknown dialect annotation type: %s`, a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch r.Dialect.DateTimeFormat {
|
|
|
|
case "RFC3339", "RFC3339Nano":
|
|
|
|
default:
|
|
|
|
return fmt.Errorf(`unknown dialect date time format: %s`, r.Dialect.DateTimeFormat)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-09-14 04:01:07 +00:00
|
|
|
func nowFunc(now time.Time) values.Function {
|
2018-10-24 16:33:43 +00:00
|
|
|
timeVal := values.NewTime(values.ConvertTime(now))
|
2018-09-14 04:01:07 +00:00
|
|
|
ftype := semantic.NewFunctionType(semantic.FunctionSignature{
|
2018-10-31 18:39:36 +00:00
|
|
|
Return: semantic.Time,
|
2018-09-14 04:01:07 +00:00
|
|
|
})
|
|
|
|
call := func(args values.Object) (values.Value, error) {
|
|
|
|
return timeVal, nil
|
|
|
|
}
|
|
|
|
sideEffect := false
|
|
|
|
return values.NewFunction("now", ftype, call, sideEffect)
|
|
|
|
}
|
|
|
|
|
|
|
|
func toSpec(p *ast.Program, now func() time.Time) (*flux.Spec, error) {
|
|
|
|
itrp := flux.NewInterpreter()
|
|
|
|
itrp.SetOption("now", nowFunc(now()))
|
2018-10-31 18:39:36 +00:00
|
|
|
semProg, err := semantic.New(p)
|
2018-09-14 04:01:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := itrp.Eval(semProg); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return flux.ToSpec(itrp, itrp.SideEffects()...), nil
|
|
|
|
}
|
|
|
|
|
2018-09-06 18:09:52 +00:00
|
|
|
// ProxyRequest returns a request to proxy from the flux.
|
2018-09-11 22:56:51 +00:00
|
|
|
func (r QueryRequest) ProxyRequest() (*query.ProxyRequest, error) {
|
2018-09-14 04:01:07 +00:00
|
|
|
return r.proxyRequest(time.Now)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r QueryRequest) proxyRequest(now func() time.Time) (*query.ProxyRequest, error) {
|
2018-09-05 14:33:10 +00:00
|
|
|
if err := r.Validate(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-09-04 21:08:00 +00:00
|
|
|
// Query is preferred over spec
|
2018-09-06 18:09:52 +00:00
|
|
|
var compiler flux.Compiler
|
2018-09-04 21:08:00 +00:00
|
|
|
if r.Query != "" {
|
2018-09-11 22:56:51 +00:00
|
|
|
compiler = lang.FluxCompiler{
|
2018-09-04 21:08:00 +00:00
|
|
|
Query: r.Query,
|
|
|
|
}
|
2018-09-14 04:01:07 +00:00
|
|
|
} else if r.AST != nil {
|
|
|
|
var err error
|
|
|
|
r.Spec, err = toSpec(r.AST, now)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
compiler = lang.SpecCompiler{
|
|
|
|
Spec: r.Spec,
|
|
|
|
}
|
2018-09-04 21:08:00 +00:00
|
|
|
} else if r.Spec != nil {
|
2018-09-11 22:56:51 +00:00
|
|
|
compiler = lang.SpecCompiler{
|
2018-09-04 21:08:00 +00:00
|
|
|
Spec: r.Spec,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delimiter, _ := utf8.DecodeRuneInString(r.Dialect.Delimiter)
|
|
|
|
|
|
|
|
noHeader := false
|
|
|
|
if r.Dialect.Header != nil {
|
|
|
|
noHeader = !*r.Dialect.Header
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(nathanielc): Use commentPrefix and dateTimeFormat
|
|
|
|
// once they are supported.
|
2018-09-11 22:56:51 +00:00
|
|
|
return &query.ProxyRequest{
|
|
|
|
Request: query.Request{
|
2018-09-04 21:08:00 +00:00
|
|
|
OrganizationID: r.org.ID,
|
|
|
|
Compiler: compiler,
|
|
|
|
},
|
|
|
|
Dialect: csv.Dialect{
|
|
|
|
ResultEncoderConfig: csv.ResultEncoderConfig{
|
|
|
|
NoHeader: noHeader,
|
|
|
|
Delimiter: delimiter,
|
|
|
|
Annotations: r.Dialect.Annotations,
|
|
|
|
},
|
|
|
|
},
|
2018-09-05 14:33:10 +00:00
|
|
|
}, nil
|
2018-09-04 21:08:00 +00:00
|
|
|
}
|
|
|
|
|
2018-09-12 21:10:09 +00:00
|
|
|
// QueryRequestFromProxyRequest converts a query.ProxyRequest into a QueryRequest.
|
|
|
|
// The ProxyRequest must contain supported compilers and dialects otherwise an error occurs.
|
|
|
|
func QueryRequestFromProxyRequest(req *query.ProxyRequest) (*QueryRequest, error) {
|
|
|
|
qr := new(QueryRequest)
|
|
|
|
switch c := req.Request.Compiler.(type) {
|
|
|
|
case lang.FluxCompiler:
|
|
|
|
qr.Type = "flux"
|
|
|
|
qr.Query = c.Query
|
|
|
|
case lang.SpecCompiler:
|
|
|
|
qr.Type = "flux"
|
|
|
|
qr.Spec = c.Spec
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unsupported compiler %T", c)
|
|
|
|
}
|
|
|
|
switch d := req.Dialect.(type) {
|
|
|
|
case *csv.Dialect:
|
|
|
|
var header = !d.ResultEncoderConfig.NoHeader
|
|
|
|
qr.Dialect.Header = &header
|
|
|
|
qr.Dialect.Delimiter = string(d.ResultEncoderConfig.Delimiter)
|
|
|
|
qr.Dialect.CommentPrefix = "#"
|
|
|
|
qr.Dialect.DateTimeFormat = "RFC3339"
|
|
|
|
qr.Dialect.Annotations = d.ResultEncoderConfig.Annotations
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unsupported dialect %T", d)
|
|
|
|
}
|
|
|
|
|
|
|
|
return qr, nil
|
|
|
|
}
|
|
|
|
|
2018-09-04 21:08:00 +00:00
|
|
|
func decodeQueryRequest(ctx context.Context, r *http.Request, svc platform.OrganizationService) (*QueryRequest, error) {
|
|
|
|
var req QueryRequest
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req = req.WithDefaults()
|
|
|
|
err := req.Validate()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.org, err = queryOrganization(ctx, r, svc)
|
|
|
|
return &req, err
|
|
|
|
}
|
|
|
|
|
2018-09-13 15:39:08 +00:00
|
|
|
func decodeProxyQueryRequest(ctx context.Context, r *http.Request, auth *platform.Authorization, svc platform.OrganizationService) (*query.ProxyRequest, error) {
|
2018-09-04 21:08:00 +00:00
|
|
|
req, err := decodeQueryRequest(ctx, r, svc)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-09-13 15:39:08 +00:00
|
|
|
|
|
|
|
pr, err := req.ProxyRequest()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pr.Request.Authorization = auth
|
|
|
|
return pr, nil
|
2018-09-04 21:08:00 +00:00
|
|
|
}
|