diff --git a/.gitignore b/.gitignore index a90fe4d4865..a11581d866f 100644 --- a/.gitignore +++ b/.gitignore @@ -35,34 +35,10 @@ test.key /editorconfig-checker /staticcheck -ui/node_modules -ui/coverage -ui/npm-error.log -ui/build -ui/.cache -ui/package-lock.json -ui/junit.xml - -# e2e test artifacts -ui/cypress/screenshots -ui/cypress/videos - -ui/src/api/.gitignore -ui/src/api/.openapi-generator-ignore -ui/src/api/.openapi-generator/VERSION -ui/src/api/git_push.sh - -# UI generated typescript types -ui/src/client/generatedRoutes.ts - -http/swagger_gen.go - -# The below files are generated with make generate -# These are used with the assests go build tag. -chronograf/canned/bin_gen.go -chronograf/dist/dist_gen.go -chronograf/server/swagger_gen.go -http/swagger_gen.go +# Generated static assets +/static/data/* +!/static/data/swagger*.yml +/static/static_gen.go # Ignore TSM/TSI testdata binary files tsdb/tsi1/testdata @@ -140,8 +116,6 @@ man/*.1.gz # test outputs /test-results.xml junit-results -cypress/screenshots -cypress/videos # profile data /prof @@ -158,6 +132,5 @@ cypress/videos .influxdbv2/ .profile .rustup/ -.yarnrc go/ goreleaser-install diff --git a/CHANGELOG.md b/CHANGELOG.md index 554ca84ecd6..6ab842dba6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Because of the version bump to `go`, the macOS build for this release requires a 1. [21869](https://github.com/influxdata/influxdb/pull/21869): Specify which fields are missing when rejecting an incomplete onboarding request. 1. [21864](https://github.com/influxdata/influxdb/pull/21864): Systemd unit should block on startup until http endpoint is ready 1. [21839](https://github.com/influxdata/influxdb/pull/21839): Fix display and parsing of `influxd upgrade` CLI prompts in PowerShell. +1. [21898](https://github.com/influxdata/influxdb/pull/21898): Removed unused `chronograf-migator` package & chronograf API service, and updated various "chronograf" references. ## v2.0.7 [2021-06-04] ---------------------- diff --git a/Makefile b/Makefile index 3969cd58fc5..2e93e291ae5 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ # SUBDIRS are directories that have their own Makefile. # It is required that all SUBDIRS have the `all` and `clean` targets. -SUBDIRS := http ui chronograf storage +SUBDIRS := static storage export GOPATH=$(shell go env GOPATH) export GOOS=$(shell go env GOOS) @@ -69,12 +69,6 @@ SOURCES := $(shell find . -name '*.go' -not -name '*_test.go') go.mod go.sum # All go source files excluding the vendored sources. SOURCES_NO_VENDOR := $(shell find . -path ./vendor -prune -o -name "*.go" -not -name '*_test.go' -print) -# All assets for chronograf -UISOURCES := $(shell find ui -type f -not \( -path ui/build/\* -o -path ui/node_modules/\* -o -path ui/.cache/\* -o -name Makefile -prune \) ) - -# All precanned dashboards -PRECANNED := $(shell find chronograf/canned -name '*.json') - # List of binary cmds to build CMDS := \ bin/$(GOOS)/influxd @@ -139,9 +133,6 @@ checkgenerate: generate: $(SUBDIRS) -test-js: node_modules - make -C ui test - test-go: $(GO_TEST) $(GO_TEST_PATHS) @@ -176,26 +167,6 @@ clean: $(RM) -r bin $(RM) -r dist -define CHRONOGIRAFFE - ._ o o - \_`-)|_ - ,"" _\_ - ," ## | 0 0. - ," ## ,-\__ `. - ," / `--._;) - "HAI, I'm Chronogiraffe. Let's be friends!" - ," ## / -," ## / -endef -export CHRONOGIRAFFE -chronogiraffe: $(SUBDIRS) generate $(CMDS) - @echo "$$CHRONOGIRAFFE" - -run: chronogiraffe - ./bin/$(GOOS)/influxd --assets-path=ui/build - -run-e2e: chronogiraffe - ./bin/$(GOOS)/influxd --assets-path=ui/build --e2e-testing --store=memory - # generate feature flags flags: $(GO_GENERATE) ./kit/feature @@ -216,4 +187,4 @@ dshell: dshell-image @docker container run --rm -p 8086:8086 -p 8080:8080 -u $(shell id -u) -it -v $(shell pwd):/code -w /code influxdb:dshell # .PHONY targets represent actions that do not create an actual file. -.PHONY: all $(SUBDIRS) run fmt checkfmt tidy checktidy checkgenerate test test-go test-js test-go-race test-tls bench clean node_modules vet nightly chronogiraffe dist ping protoc e2e run-e2e influxd libflux flags dshell dclean docker-image-flux docker-image-influx pkg-config +.PHONY: all $(SUBDIRS) run fmt checkfmt tidy checktidy checkgenerate test test-go test-js test-go-race test-tls bench clean node_modules vet nightly chronogiraffe dist ping protoc e2e influxd libflux flags dshell dclean docker-image-flux docker-image-influx pkg-config diff --git a/chronograf/.bumpversion.cfg b/chronograf/.bumpversion.cfg deleted file mode 100644 index 1036c3b5e24..00000000000 --- a/chronograf/.bumpversion.cfg +++ /dev/null @@ -1,14 +0,0 @@ -[bumpversion] -current_version = 1.5.0.0 -files = README.md server/swagger.json server/swagger_v2.yml -parse = (?P\d+)\.(?P\d+)\.(?P\d+)\.(?P\d+) -serialize = {major}.{minor}.{patch}.{release} - -[bumpversion:part:release] - -[bumpversion:file:ui/package.json] -search = "version": "{current_version}" -parse = (?P\d+)\.(?P\d+)\.(?P\d+)-(?P\d+) -serialize = {major}.{minor}.{patch}-{release} -replace = "version": "{new_version}" - diff --git a/chronograf/.kapacitor/alerts.go b/chronograf/.kapacitor/alerts.go deleted file mode 100644 index b4f8e47000b..00000000000 --- a/chronograf/.kapacitor/alerts.go +++ /dev/null @@ -1,67 +0,0 @@ -package kapacitor - -import ( - "bytes" - "encoding/json" - "regexp" - "strings" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/kapacitor/pipeline" - "github.com/influxdata/kapacitor/pipeline/tick" -) - -// AlertServices generates alert chaining methods to be attached to an alert from all rule Services -func AlertServices(rule chronograf.AlertRule) (string, error) { - node, err := addAlertNodes(rule.AlertNodes) - if err != nil { - return "", err - } - - if err := ValidateAlert(node); err != nil { - return "", err - } - return node, nil -} - -func addAlertNodes(handlers chronograf.AlertNodes) (string, error) { - octets, err := json.Marshal(&handlers) - if err != nil { - return "", err - } - - stream := &pipeline.StreamNode{} - pipe := pipeline.CreatePipelineSources(stream) - from := stream.From() - node := from.Alert() - if err = json.Unmarshal(octets, node); err != nil { - return "", err - } - - aster := tick.AST{} - err = aster.Build(pipe) - if err != nil { - return "", err - } - - var buf bytes.Buffer - aster.Program.Format(&buf, "", false) - rawTick := buf.String() - return toOldSchema(rawTick), nil -} - -var ( - removeID = regexp.MustCompile(`(?m)\s*\.id\(.*\)$`) // Remove to use ID variable - removeMessage = regexp.MustCompile(`(?m)\s*\.message\(.*\)$`) // Remove to use message variable - removeDetails = regexp.MustCompile(`(?m)\s*\.details\(.*\)$`) // Remove to use details variable - removeHistory = regexp.MustCompile(`(?m)\s*\.history\(21\)$`) // Remove default history -) - -func toOldSchema(rawTick string) string { - rawTick = strings.Replace(rawTick, "stream\n |from()\n |alert()", "", -1) - rawTick = removeID.ReplaceAllString(rawTick, "") - rawTick = removeMessage.ReplaceAllString(rawTick, "") - rawTick = removeDetails.ReplaceAllString(rawTick, "") - rawTick = removeHistory.ReplaceAllString(rawTick, "") - return rawTick -} diff --git a/chronograf/.kapacitor/alerts_test.go b/chronograf/.kapacitor/alerts_test.go deleted file mode 100644 index ff703d36414..00000000000 --- a/chronograf/.kapacitor/alerts_test.go +++ /dev/null @@ -1,228 +0,0 @@ -package kapacitor - -import ( - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestAlertServices(t *testing.T) { - tests := []struct { - name string - rule chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test several valid services", - rule: chronograf.AlertRule{ - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - }, - want: `alert() - .email() - .victorOps() - .slack() -`, - }, - { - name: "Test single valid service", - rule: chronograf.AlertRule{ - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - }, - }, - want: `alert() - .slack() -`, - }, - { - name: "Test pushoverservice", - rule: chronograf.AlertRule{ - AlertNodes: chronograf.AlertNodes{ - Pushover: []*chronograf.Pushover{ - { - Device: "asdf", - Title: "asdf", - Sound: "asdf", - URL: "http://moo.org", - URLTitle: "influxdata", - }, - }, - }, - }, - want: `alert() - .pushover() - .device('asdf') - .title('asdf') - .uRL('http://moo.org') - .uRLTitle('influxdata') - .sound('asdf') -`, - }, - { - name: "Test single valid service and property", - rule: chronograf.AlertRule{ - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{ - { - Channel: "#general", - }, - }, - }, - }, - want: `alert() - .slack() - .channel('#general') -`, - }, - { - name: "Test tcp", - rule: chronograf.AlertRule{ - AlertNodes: chronograf.AlertNodes{ - TCPs: []*chronograf.TCP{ - { - Address: "myaddress:22", - }, - }, - }, - }, - want: `alert() - .tcp('myaddress:22') -`, - }, - { - name: "Test log", - rule: chronograf.AlertRule{ - AlertNodes: chronograf.AlertNodes{ - Log: []*chronograf.Log{ - { - FilePath: "/tmp/alerts.log", - }, - }, - }, - }, - want: `alert() - .log('/tmp/alerts.log') -`, - }, - { - name: "Test http as post", - rule: chronograf.AlertRule{ - AlertNodes: chronograf.AlertNodes{ - Posts: []*chronograf.Post{ - { - URL: "http://myaddress", - }, - }, - }, - }, - want: `alert() - .post('http://myaddress') -`, - }, - { - name: "Test post with headers", - rule: chronograf.AlertRule{ - AlertNodes: chronograf.AlertNodes{ - Posts: []*chronograf.Post{ - { - URL: "http://myaddress", - Headers: map[string]string{"key": "value"}, - }, - }, - }, - }, - want: `alert() - .post('http://myaddress') - .header('key', 'value') -`, - }, - } - for _, tt := range tests { - got, err := AlertServices(tt.rule) - if (err != nil) != tt.wantErr { - t.Errorf("%q. AlertServices() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if tt.wantErr { - continue - } - formatted, err := formatTick("alert()" + got) - if err != nil { - t.Errorf("%q. formatTick() error = %v", tt.name, err) - continue - } - if formatted != tt.want { - t.Errorf("%q. AlertServices() = %v, want %v", tt.name, formatted, tt.want) - } - } -} - -func Test_addAlertNodes(t *testing.T) { - tests := []struct { - name string - handlers chronograf.AlertNodes - want string - wantErr bool - }{ - { - name: "test email alerts", - handlers: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Email: []*chronograf.Email{ - { - To: []string{ - "me@me.com", "you@you.com", - }, - }, - }, - }, - want: ` - .stateChangesOnly() - .email() - .to('me@me.com') - .to('you@you.com') -`, - }, - { - name: "test pushover alerts", - handlers: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Pushover: []*chronograf.Pushover{ - { - Device: "asdf", - Title: "asdf", - Sound: "asdf", - URL: "http://moo.org", - URLTitle: "influxdata", - }, - }, - }, - want: ` - .stateChangesOnly() - .pushover() - .device('asdf') - .title('asdf') - .uRL('http://moo.org') - .uRLTitle('influxdata') - .sound('asdf') -`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := addAlertNodes(tt.handlers) - if (err != nil) != tt.wantErr { - t.Errorf("addAlertNodes() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("addAlertNodes() =\n%v\n, want\n%v", got, tt.want) - } - }) - } -} diff --git a/chronograf/.kapacitor/ast.go b/chronograf/.kapacitor/ast.go deleted file mode 100644 index ecac0931bcc..00000000000 --- a/chronograf/.kapacitor/ast.go +++ /dev/null @@ -1,502 +0,0 @@ -package kapacitor - -import ( - "encoding/json" - "regexp" - "strconv" - "strings" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/kapacitor/pipeline" - "github.com/influxdata/kapacitor/tick" - "github.com/influxdata/kapacitor/tick/ast" - "github.com/influxdata/kapacitor/tick/stateful" -) - -func varString(kapaVar string, vars map[string]tick.Var) (string, bool) { - var ok bool - v, ok := vars[kapaVar] - if !ok { - return "", ok - } - strVar, ok := v.Value.(string) - return strVar, ok -} - -func varValue(kapaVar string, vars map[string]tick.Var) (string, bool) { - var ok bool - v, ok := vars[kapaVar] - if !ok { - return "", ok - } - switch val := v.Value.(type) { - case string: - return val, true - case float64: - return strconv.FormatFloat(val, 'f', -1, 32), true - case int64: - return strconv.FormatInt(val, 10), true - case bool: - return strconv.FormatBool(val), true - case time.Time: - return val.String(), true - case *regexp.Regexp: - return val.String(), true - default: - return "", false - } -} - -func varDuration(kapaVar string, vars map[string]tick.Var) (string, bool) { - var ok bool - v, ok := vars[kapaVar] - if !ok { - return "", ok - } - durVar, ok := v.Value.(time.Duration) - if !ok { - return "", ok - } - return durVar.String(), true -} - -func varStringList(kapaVar string, vars map[string]tick.Var) ([]string, bool) { - v, ok := vars[kapaVar] - if !ok { - return nil, ok - } - list, ok := v.Value.([]tick.Var) - if !ok { - return nil, ok - } - - strs := make([]string, len(list)) - for i, l := range list { - s, ok := l.Value.(string) - if !ok { - return nil, ok - } - strs[i] = s - } - return strs, ok -} - -// WhereFilter filters the stream data in a TICKScript -type WhereFilter struct { - TagValues map[string][]string // Tags are filtered by an array of values - Operator string // Operator is == or != -} - -func varWhereFilter(vars map[string]tick.Var) (WhereFilter, bool) { - // All chronograf TICKScripts have whereFilters. - v, ok := vars["whereFilter"] - if !ok { - return WhereFilter{}, ok - } - filter := WhereFilter{} - filter.TagValues = make(map[string][]string) - - // All chronograf TICKScript's whereFilter use a lambda function. - value, ok := v.Value.(*ast.LambdaNode) - if !ok { - return WhereFilter{}, ok - } - - lambda := value.ExpressionString() - // Chronograf TICKScripts use lambda: TRUE as a pass-throug where clause - // if the script does not have a where clause set. - if lambda == "TRUE" { - return WhereFilter{}, true - } - - opSet := map[string]struct{}{} // All ops must be the same b/c queryConfig - // Otherwise the lambda function will be several "tag" op 'value' expressions. - var re = regexp.MustCompile(`(?U)"(.*)"\s+(==|!=)\s+'(.*)'`) - for _, match := range re.FindAllStringSubmatch(lambda, -1) { - tag, op, value := match[1], match[2], match[3] - opSet[op] = struct{}{} - values, ok := filter.TagValues[tag] - if !ok { - values = make([]string, 0) - } - values = append(values, value) - filter.TagValues[tag] = values - } - - // An obscure piece of the queryConfig is that the operator in ALL binary - // expressions just be the same. So, there must only be one operator - // in our opSet - if len(opSet) != 1 { - return WhereFilter{}, false - } - for op := range opSet { - if op != "==" && op != "!=" { - return WhereFilter{}, false - } - filter.Operator = op - } - return filter, true -} - -// CommonVars includes all the variables of a chronograf TICKScript -type CommonVars struct { - DB string - RP string - Measurement string - Name string - Message string - TriggerType string - GroupBy []string - Filter WhereFilter - Period string - Every string - Detail string -} - -// ThresholdVars represents the critical value where an alert occurs -type ThresholdVars struct { - Crit string -} - -// RangeVars represents the critical range where an alert occurs -type RangeVars struct { - Lower string - Upper string -} - -// RelativeVars represents the critical range and time in the past an alert occurs -type RelativeVars struct { - Shift string - Crit string -} - -// DeadmanVars represents a deadman alert -type DeadmanVars struct{} - -func extractCommonVars(vars map[string]tick.Var) (CommonVars, error) { - res := CommonVars{} - // All these variables must exist to be a chronograf TICKScript - // If any of these don't exist, then this isn't a tickscript we can process - var ok bool - res.DB, ok = varString("db", vars) - if !ok { - return CommonVars{}, ErrNotChronoTickscript - } - res.RP, ok = varString("rp", vars) - if !ok { - return CommonVars{}, ErrNotChronoTickscript - } - res.Measurement, ok = varString("measurement", vars) - if !ok { - return CommonVars{}, ErrNotChronoTickscript - } - res.Name, ok = varString("name", vars) - if !ok { - return CommonVars{}, ErrNotChronoTickscript - } - res.Message, ok = varString("message", vars) - if !ok { - return CommonVars{}, ErrNotChronoTickscript - } - res.TriggerType, ok = varString("triggerType", vars) - if !ok { - return CommonVars{}, ErrNotChronoTickscript - } - - // All chronograf TICKScripts have groupBy. Possible to be empty list though. - groups, ok := varStringList("groupBy", vars) - if !ok { - return CommonVars{}, ErrNotChronoTickscript - } - res.GroupBy = groups - - // All chronograf TICKScripts must have a whereFitler. Could be empty. - res.Filter, ok = varWhereFilter(vars) - if !ok { - return CommonVars{}, ErrNotChronoTickscript - } - - // Some chronograf TICKScripts have details associated with the alert. - // Typically, this is the body of an email alert. - if detail, ok := varString("details", vars); ok { - res.Detail = detail - } - - // Relative and Threshold alerts may have an every variables - if every, ok := varDuration("every", vars); ok { - res.Every = every - } - - // All alert types may have a period variables - if period, ok := varDuration("period", vars); ok { - res.Period = period - } - return res, nil -} - -func extractAlertVars(vars map[string]tick.Var) (interface{}, error) { - // Depending on the type of the alert the variables set will be different - alertType, ok := varString("triggerType", vars) - if !ok { - return nil, ErrNotChronoTickscript - } - - switch alertType { - case Deadman: - return &DeadmanVars{}, nil - case Threshold: - if crit, ok := varValue("crit", vars); ok { - return &ThresholdVars{ - Crit: crit, - }, nil - } - r := &RangeVars{} - // Threshold Range alerts must have both an upper and lower bound - if r.Lower, ok = varValue("lower", vars); !ok { - return nil, ErrNotChronoTickscript - } - if r.Upper, ok = varValue("upper", vars); !ok { - return nil, ErrNotChronoTickscript - } - return r, nil - case Relative: - // Relative alerts must have a time shift and critical value - r := &RelativeVars{} - if r.Shift, ok = varDuration("shift", vars); !ok { - return nil, ErrNotChronoTickscript - } - if r.Crit, ok = varValue("crit", vars); !ok { - return nil, ErrNotChronoTickscript - } - return r, nil - default: - return nil, ErrNotChronoTickscript - } -} - -// FieldFunc represents the field used as the alert value and its optional aggregate function -type FieldFunc struct { - Field string - Func string -} - -func extractFieldFunc(script chronograf.TICKScript) FieldFunc { - // If the TICKScript is relative or threshold alert with an aggregate - // then the aggregate function and field is in the form |func('field').as('value') - var re = regexp.MustCompile(`(?Um)\|(\w+)\('(.*)'\)\s*\.as\('value'\)`) - for _, match := range re.FindAllStringSubmatch(string(script), -1) { - fn, field := match[1], match[2] - return FieldFunc{ - Field: field, - Func: fn, - } - } - - // If the alert does not have an aggregate then the the value function will - // be this form: |eval(lambda: "%s").as('value') - re = regexp.MustCompile(`(?Um)\|eval\(lambda: "(.*)"\)\s*\.as\('value'\)`) - for _, match := range re.FindAllStringSubmatch(string(script), -1) { - field := match[1] - return FieldFunc{ - Field: field, - } - } - // Otherwise, if this could be a deadman alert and not have a FieldFunc - return FieldFunc{} -} - -// CritCondition represents the operators that determine when the alert should go critical -type CritCondition struct { - Operators []string -} - -func extractCrit(script chronograf.TICKScript) CritCondition { - // Threshold and relative alerts have the form .crit(lambda: "value" op crit) - // Threshold range alerts have the form .crit(lambda: "value" op lower op "value" op upper) - var re = regexp.MustCompile(`(?Um)\.crit\(lambda:\s+"value"\s+(.*)\s+crit\)`) - for _, match := range re.FindAllStringSubmatch(string(script), -1) { - op := match[1] - return CritCondition{ - Operators: []string{ - op, - }, - } - } - re = regexp.MustCompile(`(?Um)\.crit\(lambda:\s+"value"\s+(.*)\s+lower\s+(.*)\s+"value"\s+(.*)\s+upper\)`) - for _, match := range re.FindAllStringSubmatch(string(script), -1) { - lower, compound, upper := match[1], match[2], match[3] - return CritCondition{ - Operators: []string{ - lower, - compound, - upper, - }, - } - } - - // It's possible to not have a critical condition if this is - // a deadman alert - return CritCondition{} -} - -// alertType reads the TICKscript and returns the specific -// alerting type. If it is unable to determine it will -// return ErrNotChronoTickscript -func alertType(script chronograf.TICKScript) (string, error) { - t := string(script) - if strings.Contains(t, `var triggerType = 'threshold'`) { - if strings.Contains(t, `var crit = `) { - return Threshold, nil - } else if strings.Contains(t, `var lower = `) && strings.Contains(t, `var upper = `) { - return ThresholdRange, nil - } - return "", ErrNotChronoTickscript - } else if strings.Contains(t, `var triggerType = 'relative'`) { - if strings.Contains(t, `eval(lambda: float("current.value" - "past.value"))`) { - return ChangeAmount, nil - } else if strings.Contains(t, `|eval(lambda: abs(float("current.value" - "past.value")) / float("past.value") * 100.0)`) { - return ChangePercent, nil - } - return "", ErrNotChronoTickscript - } else if strings.Contains(t, `var triggerType = 'deadman'`) { - return Deadman, nil - } - return "", ErrNotChronoTickscript -} - -// Reverse converts tickscript to an AlertRule -func Reverse(script chronograf.TICKScript) (chronograf.AlertRule, error) { - rule := chronograf.AlertRule{ - Query: &chronograf.QueryConfig{}, - } - t, err := alertType(script) - if err != nil { - return rule, err - } - - scope := stateful.NewScope() - template, err := pipeline.CreateTemplatePipeline(string(script), pipeline.StreamEdge, scope, &deadman{}) - if err != nil { - return chronograf.AlertRule{}, err - } - vars := template.Vars() - - commonVars, err := extractCommonVars(vars) - if err != nil { - return rule, err - } - alertVars, err := extractAlertVars(vars) - if err != nil { - return rule, err - } - fieldFunc := extractFieldFunc(script) - critCond := extractCrit(script) - - switch t { - case Threshold, ChangeAmount, ChangePercent: - if len(critCond.Operators) != 1 { - return rule, ErrNotChronoTickscript - } - case ThresholdRange: - if len(critCond.Operators) != 3 { - return rule, ErrNotChronoTickscript - } - } - - rule.Name = commonVars.Name - rule.Trigger = commonVars.TriggerType - rule.Message = commonVars.Message - rule.Details = commonVars.Detail - rule.Query.Database = commonVars.DB - rule.Query.RetentionPolicy = commonVars.RP - rule.Query.Measurement = commonVars.Measurement - rule.Query.GroupBy.Tags = commonVars.GroupBy - if commonVars.Filter.Operator == "==" { - rule.Query.AreTagsAccepted = true - } - rule.Query.Tags = commonVars.Filter.TagValues - - if t == Deadman { - rule.TriggerValues.Period = commonVars.Period - } else { - rule.Query.GroupBy.Time = commonVars.Period - rule.Every = commonVars.Every - if fieldFunc.Func != "" { - rule.Query.Fields = []chronograf.Field{ - { - Type: "func", - Value: fieldFunc.Func, - Args: []chronograf.Field{ - { - Value: fieldFunc.Field, - Type: "field", - }, - }, - }, - } - } else { - rule.Query.Fields = []chronograf.Field{ - { - Type: "field", - Value: fieldFunc.Field, - }, - } - } - } - - switch t { - case ChangeAmount, ChangePercent: - rule.TriggerValues.Change = t - rule.TriggerValues.Operator, err = chronoOperator(critCond.Operators[0]) - if err != nil { - return rule, ErrNotChronoTickscript - } - v, ok := alertVars.(*RelativeVars) - if !ok { - return rule, ErrNotChronoTickscript - } - rule.TriggerValues.Value = v.Crit - rule.TriggerValues.Shift = v.Shift - case Threshold: - rule.TriggerValues.Operator, err = chronoOperator(critCond.Operators[0]) - if err != nil { - return rule, ErrNotChronoTickscript - } - v, ok := alertVars.(*ThresholdVars) - if !ok { - return rule, ErrNotChronoTickscript - } - rule.TriggerValues.Value = v.Crit - case ThresholdRange: - rule.TriggerValues.Operator, err = chronoRangeOperators(critCond.Operators) - v, ok := alertVars.(*RangeVars) - if !ok { - return rule, ErrNotChronoTickscript - } - rule.TriggerValues.Value = v.Lower - rule.TriggerValues.RangeValue = v.Upper - } - - p, err := pipeline.CreatePipeline(string(script), pipeline.StreamEdge, stateful.NewScope(), &deadman{}, vars) - if err != nil { - return chronograf.AlertRule{}, err - } - - err = extractAlertNodes(p, &rule) - return rule, err -} - -func extractAlertNodes(p *pipeline.Pipeline, rule *chronograf.AlertRule) error { - return p.Walk(func(n pipeline.Node) error { - switch node := n.(type) { - case *pipeline.AlertNode: - octets, err := json.MarshalIndent(node, "", " ") - if err != nil { - return err - } - return json.Unmarshal(octets, &rule.AlertNodes) - } - return nil - }) -} diff --git a/chronograf/.kapacitor/ast_test.go b/chronograf/.kapacitor/ast_test.go deleted file mode 100644 index 3938ebcd18b..00000000000 --- a/chronograf/.kapacitor/ast_test.go +++ /dev/null @@ -1,1569 +0,0 @@ -package kapacitor - -import ( - "reflect" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestReverse(t *testing.T) { - tests := []struct { - name string - script chronograf.TICKScript - want chronograf.AlertRule - wantErr bool - }{ - { - name: "simple stream tickscript", - script: chronograf.TICKScript(` - var name = 'name' - var triggerType = 'threshold' - var every = 30s - var period = 10m - var groupBy = ['host', 'cluster_id'] - var db = 'telegraf' - var rp = 'autogen' - var measurement = 'cpu' - var message = 'message' - var details = 'details' - var crit = 90 - var idVar = name + ':{{.Group}}' - var idTag = 'alertID' - var levelTag = 'level' - var messageField = 'message' - var durationField = 'duration' - var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - - var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email('howdy@howdy.com', 'doody@doody.com') - .log('/tmp/alerts.log') - .post('http://backin.tm') - .endpoint('myendpoint') - .header('key', 'value') - `), - - want: chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Slack: []*chronograf.Slack{ - {}, - }, - VictorOps: []*chronograf.VictorOps{ - {}, - }, - Email: []*chronograf.Email{ - { - To: []string{"howdy@howdy.com", "doody@doody.com"}, - }, - }, - Log: []*chronograf.Log{ - { - FilePath: "/tmp/alerts.log", - }, - }, - Posts: []*chronograf.Post{ - { - URL: "http://backin.tm", - Headers: map[string]string{"key": "value"}, - }, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Message: "message", - Details: "details", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - RetentionPolicy: "autogen", - Measurement: "cpu", - Fields: []chronograf.Field{ - { - Value: "mean", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - Type: "func", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m0s", - Tags: []string{"host", "cluster_id"}, - }, - Tags: map[string][]string{ - "cpu": []string{ - "cpu_total", - }, - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - }, - AreTagsAccepted: true, - }, - }, - }, - { - name: "Test Threshold", - script: `var db = 'telegraf' - - var rp = 'autogen' - - var measurement = 'cpu' - - var groupBy = ['host', 'cluster_id'] - - var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - - var period = 10m - - var every = 30s - - var name = 'name' - - var idVar = name + ':{{.Group}}' - - var message = 'message' - - var idTag = 'alertID' - - var levelTag = 'level' - - var messageField = 'message' - - var durationField = 'duration' - - var outputDB = 'chronograf' - - var outputRP = 'autogen' - - var outputMeasurement = 'alerts' - - var triggerType = 'threshold' - - var crit = 90 - - var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - - var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email() - - trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - - trigger - |httpOut('output')`, - want: chronograf.AlertRule{ - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - Type: "func", - }, - }, - Tags: map[string][]string{ - "cpu": []string{"cpu_total"}, - "host": []string{"acc-0eabc309-eu-west-1-data-3", "prod"}, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m0s", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - Every: "30s", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - - Slack: []*chronograf.Slack{ - {}, - }, - VictorOps: []*chronograf.VictorOps{ - {}, - }, - Email: []*chronograf.Email{ - { - To: []string{}, - }, - }, - }, - Message: "message", - Trigger: "threshold", - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90", - }, - Name: "name", - }, - }, - { - name: "Test haproxy string comparison", - script: `var db = 'influxdb' - - var rp = 'autogen' - - var measurement = 'haproxy' - - var groupBy = ['pxname'] - - var whereFilter = lambda: TRUE - - var period = 10s - - var every = 10s - - var name = 'haproxy' - - var idVar = name + ':{{.Group}}' - - var message = 'Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} ' - - var idTag = 'alertID' - - var levelTag = 'level' - - var messageField = 'message' - - var durationField = 'duration' - - var outputDB = 'chronograf' - - var outputRP = 'autogen' - - var outputMeasurement = 'alerts' - - var triggerType = 'threshold' - - var details = 'Email template' - - var crit = 'DOWN' - - var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |last('status') - .as('value') - - var trigger = data - |alert() - .crit(lambda: "value" == crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .details(details) - .email() - - trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - - trigger - |httpOut('output') - `, - want: chronograf.AlertRule{ - Name: "haproxy", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Email: []*chronograf.Email{ - {To: []string{}}, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "equal to", - Value: "DOWN", - }, - Every: "10s", - Message: `Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} `, - Details: "Email template", - Query: &chronograf.QueryConfig{ - Database: "influxdb", - RetentionPolicy: "autogen", - Measurement: "haproxy", - Fields: []chronograf.Field{ - { - Value: "last", - Args: []chronograf.Field{ - { - Value: "status", - Type: "field", - }, - }, - Type: "func", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10s", - Tags: []string{"pxname"}, - }, - AreTagsAccepted: false, - }, - }, - }, - { - name: "Test haproxy", - script: `var db = 'influxdb' - - var rp = 'autogen' - - var measurement = 'haproxy' - - var groupBy = ['pxname'] - - var whereFilter = lambda: TRUE - - var period = 10s - - var every = 10s - - var name = 'haproxy' - - var idVar = name + ':{{.Group}}' - - var message = 'Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} ' - - var idTag = 'alertID' - - var levelTag = 'level' - - var messageField = 'message' - - var durationField = 'duration' - - var outputDB = 'chronograf' - - var outputRP = 'autogen' - - var outputMeasurement = 'alerts' - - var triggerType = 'threshold' - - var details = 'Email template' - - var crit = 'DOWN' - - var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |last('status') - .as('value') - - var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .details(details) - .email() - - trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - - trigger - |httpOut('output') - `, - want: chronograf.AlertRule{ - Name: "haproxy", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Email: []*chronograf.Email{ - {To: []string{}}, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "DOWN", - }, - Every: "10s", - Message: `Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} `, - Details: "Email template", - Query: &chronograf.QueryConfig{ - Database: "influxdb", - RetentionPolicy: "autogen", - Measurement: "haproxy", - Fields: []chronograf.Field{ - { - Value: "last", - Args: []chronograf.Field{ - { - Value: "status", - Type: "field", - }, - }, - Type: "func", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10s", - Tags: []string{"pxname"}, - }, - AreTagsAccepted: false, - }, - }, - }, - { - name: "Test valid template alert with detail", - script: `var db = 'telegraf' - - var rp = 'autogen' - - var measurement = 'cpu' - - var groupBy = ['host', 'cluster_id'] - - var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - - var period = 10m - - var every = 30s - - var name = 'name' - - var idVar = name + ':{{.Group}}' - - var message = 'message' - - var idTag = 'alertID' - - var levelTag = 'level' - - var messageField = 'message' - - var durationField = 'duration' - - var outputDB = 'chronograf' - - var outputRP = 'autogen' - - var outputMeasurement = 'alerts' - - var triggerType = 'threshold' - - var details = 'details' - - var crit = 90 - - var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - - var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .details(details) - .slack() - .victorOps() - .email() - - trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - - trigger - |httpOut('output') - `, - want: chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Slack: []*chronograf.Slack{ - {}, - }, - VictorOps: []*chronograf.VictorOps{ - {}, - }, - Email: []*chronograf.Email{ - {To: []string{}}, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Message: "message", - Details: "details", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - Type: "func", - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m0s", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - }, - }, - { - name: "Test valid threshold inside range", - script: `var db = 'telegraf' - - var rp = 'autogen' - - var measurement = 'cpu' - - var groupBy = ['host', 'cluster_id'] - - var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - - var period = 10m - - var every = 30s - - var name = 'name' - - var idVar = name + ':{{.Group}}' - - var message = 'message' - - var idTag = 'alertID' - - var levelTag = 'level' - - var messageField = 'message' - - var durationField = 'duration' - - var outputDB = 'chronograf' - - var outputRP = 'autogen' - - var outputMeasurement = 'alerts' - - var triggerType = 'threshold' - - var lower = 90 - - var upper = 100 - - var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - - var trigger = data - |alert() - .crit(lambda: "value" >= lower AND "value" <= upper) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email() - - trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - - trigger - |httpOut('output') - `, - want: chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Slack: []*chronograf.Slack{ - {}, - }, - VictorOps: []*chronograf.VictorOps{ - {}, - }, - Email: []*chronograf.Email{ - {To: []string{}}, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "inside range", - Value: "90", - RangeValue: "100", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - Type: "func", - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m0s", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - }, - }, - { - name: "Test valid threshold outside range", - script: `var db = 'telegraf' - - var rp = 'autogen' - - var measurement = 'cpu' - - var groupBy = ['host', 'cluster_id'] - - var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - - var period = 10m - - var every = 30s - - var name = 'name' - - var idVar = name + ':{{.Group}}' - - var message = 'message' - - var idTag = 'alertID' - - var levelTag = 'level' - - var messageField = 'message' - - var durationField = 'duration' - - var outputDB = 'chronograf' - - var outputRP = 'autogen' - - var outputMeasurement = 'alerts' - - var triggerType = 'threshold' - - var lower = 90 - - var upper = 100 - - var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - - var trigger = data - |alert() - .crit(lambda: "value" < lower OR "value" > upper) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email() - - trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - - trigger - |httpOut('output') - `, - want: chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Slack: []*chronograf.Slack{ - {}, - }, - VictorOps: []*chronograf.VictorOps{ - {}, - }, - Email: []*chronograf.Email{ - {To: []string{}}, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "outside range", - Value: "90", - RangeValue: "100", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - Type: "func", - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m0s", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - }, - }, - { - name: "Test threshold no aggregate", - script: `var db = 'telegraf' - - var rp = 'autogen' - - var measurement = 'cpu' - - var groupBy = ['host', 'cluster_id'] - - var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - - var name = 'name' - - var idVar = name + ':{{.Group}}' - - var message = 'message' - - var idTag = 'alertID' - - var levelTag = 'level' - - var messageField = 'message' - - var durationField = 'duration' - - var outputDB = 'chronograf' - - var outputRP = 'autogen' - - var outputMeasurement = 'alerts' - - var triggerType = 'threshold' - - var crit = 90 - - var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |eval(lambda: "usage_user") - .as('value') - - var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email() - - trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - - trigger - |httpOut('output') - `, - want: chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Slack: []*chronograf.Slack{ - {}, - }, - VictorOps: []*chronograf.VictorOps{ - {}, - }, - Email: []*chronograf.Email{ - {To: []string{}}, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90", - }, - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - }, - }, - { - name: "Test relative alert", - script: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var every = 30s - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'relative' - -var shift = 1m - -var crit = 90 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - -var past = data - |shift(shift) - -var current = data - -var trigger = past - |join(current) - .as('past', 'current') - |eval(lambda: abs(float("current.value" - "past.value")) / float("past.value") * 100.0) - .keep() - .as('value') - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email() - -trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - want: chronograf.AlertRule{ - Name: "name", - Trigger: "relative", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Slack: []*chronograf.Slack{ - {}, - }, - VictorOps: []*chronograf.VictorOps{ - {}, - }, - Email: []*chronograf.Email{ - {To: []string{}}, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Change: "% change", - Shift: "1m0s", - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - Type: "func", - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m0s", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - }, - }, - { - name: "Test relative change", - script: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var every = 30s - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'relative' - -var shift = 1m - -var crit = 90 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - -var past = data - |shift(shift) - -var current = data - -var trigger = past - |join(current) - .as('past', 'current') - |eval(lambda: float("current.value" - "past.value")) - .keep() - .as('value') - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email() - -trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - want: chronograf.AlertRule{ - Name: "name", - Trigger: "relative", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Slack: []*chronograf.Slack{ - {}, - }, - VictorOps: []*chronograf.VictorOps{ - {}, - }, - Email: []*chronograf.Email{ - {To: []string{}}, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Change: "change", - Shift: "1m0s", - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - Type: "func", - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m0s", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - }, - }, - { - name: "Test deadman", - script: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'deadman' - -var threshold = 0.0 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - -var trigger = data - |deadman(threshold, period) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email() - -trigger - |eval(lambda: "emitted") - .as('value') - .keep('value', messageField, durationField) - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - want: chronograf.AlertRule{ - Name: "name", - Trigger: "deadman", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - Slack: []*chronograf.Slack{ - {}, - }, - VictorOps: []*chronograf.VictorOps{ - {}, - }, - Email: []*chronograf.Email{ - {To: []string{}}, - }, - }, - TriggerValues: chronograf.TriggerValues{ - Period: "10m0s", - }, - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - }, - }, - { - name: "Test threshold lambda", - script: `var db = '_internal' - -var rp = 'monitor' - -var measurement = 'cq' - -var groupBy = [] - -var whereFilter = lambda: TRUE - -var name = 'rule 1' - -var idVar = name + ':{{.Group}}' - -var message = '' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var crit = 90000 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |eval(lambda: "queryOk") - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - -trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - want: chronograf.AlertRule{ - Name: "rule 1", - Trigger: "threshold", - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90000", - }, - Every: "", - Message: "", - Details: "", - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - }, - Query: &chronograf.QueryConfig{ - Database: "_internal", - RetentionPolicy: "monitor", - Measurement: "cq", - Fields: []chronograf.Field{ - { - Value: "queryOk", - Type: "field", - }, - }, - GroupBy: chronograf.GroupBy{ - Tags: []string{}, - }, - AreTagsAccepted: false, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := Reverse(tt.script) - if (err != nil) != tt.wantErr { - t.Errorf("reverse error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("reverse = %s", cmp.Diff(got, tt.want)) - if tt.want.Query != nil { - if got.Query == nil { - t.Errorf("reverse = got nil QueryConfig") - } else if !cmp.Equal(*got.Query, *tt.want.Query) { - t.Errorf("reverse = QueryConfig not equal %s", cmp.Diff(*got.Query, *tt.want.Query)) - } - } - } - }) - } -} diff --git a/chronograf/.kapacitor/client.go b/chronograf/.kapacitor/client.go deleted file mode 100644 index 19ea2aeee10..00000000000 --- a/chronograf/.kapacitor/client.go +++ /dev/null @@ -1,415 +0,0 @@ -package kapacitor - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/id" - client "github.com/influxdata/kapacitor/client/v1" -) - -const ( - // Prefix is prepended to the ID of all alerts - Prefix = "chronograf-v1-" - - // FetchRate is the rate Paginating Kapacitor Clients will consume responses - FetchRate = 100 -) - -// Client communicates to kapacitor -type Client struct { - URL string - Username string - Password string - InsecureSkipVerify bool - ID chronograf.ID - Ticker chronograf.Ticker - kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) -} - -// KapaClient represents a connection to a kapacitor instance -type KapaClient interface { - CreateTask(opt client.CreateTaskOptions) (client.Task, error) - Task(link client.Link, opt *client.TaskOptions) (client.Task, error) - ListTasks(opt *client.ListTasksOptions) ([]client.Task, error) - UpdateTask(link client.Link, opt client.UpdateTaskOptions) (client.Task, error) - DeleteTask(link client.Link) error -} - -// NewClient creates a client that interfaces with Kapacitor tasks -func NewClient(url, username, password string, insecureSkipVerify bool) *Client { - return &Client{ - URL: url, - Username: username, - Password: password, - InsecureSkipVerify: insecureSkipVerify, - ID: &id.UUID{}, - Ticker: &Alert{}, - kapaClient: NewKapaClient, - } -} - -// Task represents a running kapacitor task -type Task struct { - ID string // Kapacitor ID - Href string // Kapacitor relative URI - HrefOutput string // Kapacitor relative URI to HTTPOutNode - Rule chronograf.AlertRule // Rule is the rule that represents this Task - TICKScript chronograf.TICKScript // TICKScript is the running script -} - -// NewTask creates a task from a kapacitor client task -func NewTask(task *client.Task) *Task { - dbrps := make([]chronograf.DBRP, len(task.DBRPs)) - for i := range task.DBRPs { - dbrps[i].DB = task.DBRPs[i].Database - dbrps[i].RP = task.DBRPs[i].RetentionPolicy - } - - script := chronograf.TICKScript(task.TICKscript) - rule, err := Reverse(script) - if err != nil { - rule = chronograf.AlertRule{ - Name: task.ID, - Query: nil, - } - } - - rule.ID = task.ID - rule.TICKScript = script - rule.Type = task.Type.String() - rule.DBRPs = dbrps - rule.Status = task.Status.String() - rule.Executing = task.Executing - rule.Error = task.Error - rule.Created = task.Created - rule.Modified = task.Modified - rule.LastEnabled = task.LastEnabled - return &Task{ - ID: task.ID, - Href: task.Link.Href, - HrefOutput: HrefOutput(task.ID), - Rule: rule, - } -} - -// HrefOutput returns the link to a kapacitor task httpOut Node given an id -func HrefOutput(ID string) string { - return fmt.Sprintf("/kapacitor/v1/tasks/%s/%s", ID, HTTPEndpoint) -} - -// Href returns the link to a kapacitor task given an id -func (c *Client) Href(ID string) string { - return fmt.Sprintf("/kapacitor/v1/tasks/%s", ID) -} - -// HrefOutput returns the link to a kapacitor task httpOut Node given an id -func (c *Client) HrefOutput(ID string) string { - return HrefOutput(ID) -} - -// Create builds and POSTs a tickscript to kapacitor -func (c *Client) Create(ctx context.Context, rule chronograf.AlertRule) (*Task, error) { - var opt *client.CreateTaskOptions - var err error - if rule.Query != nil { - opt, err = c.createFromQueryConfig(rule) - } else { - opt, err = c.createFromTick(rule) - } - - if err != nil { - return nil, err - } - - kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) - if err != nil { - return nil, err - } - - task, err := kapa.CreateTask(*opt) - if err != nil { - return nil, err - } - - return NewTask(&task), nil -} - -func (c *Client) createFromTick(rule chronograf.AlertRule) (*client.CreateTaskOptions, error) { - dbrps := make([]client.DBRP, len(rule.DBRPs)) - for i := range rule.DBRPs { - dbrps[i] = client.DBRP{ - Database: rule.DBRPs[i].DB, - RetentionPolicy: rule.DBRPs[i].RP, - } - } - - status := client.Enabled - if rule.Status != "" { - if err := status.UnmarshalText([]byte(rule.Status)); err != nil { - return nil, err - } - } - - taskType := client.StreamTask - if rule.Type != "stream" { - if err := taskType.UnmarshalText([]byte(rule.Type)); err != nil { - return nil, err - } - } - - return &client.CreateTaskOptions{ - ID: rule.Name, - Type: taskType, - DBRPs: dbrps, - TICKscript: string(rule.TICKScript), - Status: status, - }, nil -} - -func (c *Client) createFromQueryConfig(rule chronograf.AlertRule) (*client.CreateTaskOptions, error) { - id, err := c.ID.Generate() - if err != nil { - return nil, err - } - - script, err := c.Ticker.Generate(rule) - if err != nil { - return nil, err - } - - kapaID := Prefix + id - return &client.CreateTaskOptions{ - ID: kapaID, - Type: toTask(rule.Query), - DBRPs: []client.DBRP{{Database: rule.Query.Database, RetentionPolicy: rule.Query.RetentionPolicy}}, - TICKscript: string(script), - Status: client.Enabled, - }, nil -} - -// Delete removes tickscript task from kapacitor -func (c *Client) Delete(ctx context.Context, href string) error { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) - if err != nil { - return err - } - return kapa.DeleteTask(client.Link{Href: href}) -} - -func (c *Client) updateStatus(ctx context.Context, href string, status client.TaskStatus) (*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) - if err != nil { - return nil, err - } - - opts := client.UpdateTaskOptions{ - Status: status, - } - - task, err := kapa.UpdateTask(client.Link{Href: href}, opts) - if err != nil { - return nil, err - } - - return NewTask(&task), nil -} - -// Disable changes the tickscript status to disabled for a given href. -func (c *Client) Disable(ctx context.Context, href string) (*Task, error) { - return c.updateStatus(ctx, href, client.Disabled) -} - -// Enable changes the tickscript status to disabled for a given href. -func (c *Client) Enable(ctx context.Context, href string) (*Task, error) { - return c.updateStatus(ctx, href, client.Enabled) -} - -// Status returns the status of a task in kapacitor -func (c *Client) Status(ctx context.Context, href string) (string, error) { - s, err := c.status(ctx, href) - if err != nil { - return "", err - } - - return s.String(), nil -} - -func (c *Client) status(ctx context.Context, href string) (client.TaskStatus, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) - if err != nil { - return 0, err - } - task, err := kapa.Task(client.Link{Href: href}, nil) - if err != nil { - return 0, err - } - - return task.Status, nil -} - -// All returns all tasks in kapacitor -func (c *Client) All(ctx context.Context) (map[string]*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) - if err != nil { - return nil, err - } - - // Only get the status, id and link section back - opts := &client.ListTasksOptions{} - tasks, err := kapa.ListTasks(opts) - if err != nil { - return nil, err - } - - all := map[string]*Task{} - for _, task := range tasks { - all[task.ID] = NewTask(&task) - } - return all, nil -} - -// Reverse builds a chronograf.AlertRule and its QueryConfig from a tickscript -func (c *Client) Reverse(id string, script chronograf.TICKScript) chronograf.AlertRule { - rule, err := Reverse(script) - if err != nil { - return chronograf.AlertRule{ - ID: id, - Name: id, - Query: nil, - TICKScript: script, - } - } - rule.ID = id - rule.TICKScript = script - return rule -} - -// Get returns a single alert in kapacitor -func (c *Client) Get(ctx context.Context, id string) (*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) - if err != nil { - return nil, err - } - href := c.Href(id) - task, err := kapa.Task(client.Link{Href: href}, nil) - if err != nil { - return nil, chronograf.ErrAlertNotFound - } - - return NewTask(&task), nil -} - -// Update changes the tickscript of a given id. -func (c *Client) Update(ctx context.Context, href string, rule chronograf.AlertRule) (*Task, error) { - kapa, err := c.kapaClient(c.URL, c.Username, c.Password, c.InsecureSkipVerify) - if err != nil { - return nil, err - } - - prevStatus, err := c.status(ctx, href) - if err != nil { - return nil, err - } - - var opt *client.UpdateTaskOptions - if rule.Query != nil { - opt, err = c.updateFromQueryConfig(rule) - } else { - opt, err = c.updateFromTick(rule) - } - if err != nil { - return nil, err - } - - task, err := kapa.UpdateTask(client.Link{Href: href}, *opt) - if err != nil { - return nil, err - } - - // Now enable the task if previously enabled - if prevStatus == client.Enabled { - if _, err := c.Enable(ctx, href); err != nil { - return nil, err - } - } - - return NewTask(&task), nil -} - -func (c *Client) updateFromQueryConfig(rule chronograf.AlertRule) (*client.UpdateTaskOptions, error) { - script, err := c.Ticker.Generate(rule) - if err != nil { - return nil, err - } - - // We need to disable the kapacitor task followed by enabling it during update. - return &client.UpdateTaskOptions{ - TICKscript: string(script), - Status: client.Disabled, - Type: toTask(rule.Query), - DBRPs: []client.DBRP{ - { - Database: rule.Query.Database, - RetentionPolicy: rule.Query.RetentionPolicy, - }, - }, - }, nil -} - -func (c *Client) updateFromTick(rule chronograf.AlertRule) (*client.UpdateTaskOptions, error) { - dbrps := make([]client.DBRP, len(rule.DBRPs)) - for i := range rule.DBRPs { - dbrps[i] = client.DBRP{ - Database: rule.DBRPs[i].DB, - RetentionPolicy: rule.DBRPs[i].RP, - } - } - - taskType := client.StreamTask - if rule.Type != "stream" { - if err := taskType.UnmarshalText([]byte(rule.Type)); err != nil { - return nil, err - } - } - - // We need to disable the kapacitor task followed by enabling it during update. - return &client.UpdateTaskOptions{ - TICKscript: string(rule.TICKScript), - Status: client.Disabled, - Type: taskType, - DBRPs: dbrps, - }, nil -} - -func toTask(q *chronograf.QueryConfig) client.TaskType { - if q == nil || q.RawText == nil || *q.RawText == "" { - return client.StreamTask - } - return client.BatchTask -} - -// NewKapaClient creates a Kapacitor client connection -func NewKapaClient(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - var creds *client.Credentials - if username != "" { - creds = &client.Credentials{ - Method: client.UserAuthentication, - Username: username, - Password: password, - } - } - - clnt, err := client.New(client.Config{ - URL: url, - Credentials: creds, - InsecureSkipVerify: insecureSkipVerify, - }) - - if err != nil { - return clnt, err - } - - return &PaginatingKapaClient{clnt, FetchRate}, nil -} diff --git a/chronograf/.kapacitor/client_test.go b/chronograf/.kapacitor/client_test.go deleted file mode 100644 index 4f273c886fd..00000000000 --- a/chronograf/.kapacitor/client_test.go +++ /dev/null @@ -1,1653 +0,0 @@ -package kapacitor - -import ( - "context" - "fmt" - "reflect" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/influxdata/influxdb/v2/chronograf" - client "github.com/influxdata/kapacitor/client/v1" -) - -type MockKapa struct { - ResTask client.Task - ResTasks []client.Task - TaskError error - UpdateError error - CreateError error - ListError error - DeleteError error - LastStatus client.TaskStatus - - *client.CreateTaskOptions - client.Link - *client.TaskOptions - *client.ListTasksOptions - *client.UpdateTaskOptions -} - -func (m *MockKapa) CreateTask(opt client.CreateTaskOptions) (client.Task, error) { - m.CreateTaskOptions = &opt - return m.ResTask, m.CreateError -} - -func (m *MockKapa) Task(link client.Link, opt *client.TaskOptions) (client.Task, error) { - m.Link = link - m.TaskOptions = opt - return m.ResTask, m.TaskError -} - -func (m *MockKapa) ListTasks(opt *client.ListTasksOptions) ([]client.Task, error) { - m.ListTasksOptions = opt - return m.ResTasks, m.ListError -} - -func (m *MockKapa) UpdateTask(link client.Link, opt client.UpdateTaskOptions) (client.Task, error) { - m.Link = link - m.LastStatus = opt.Status - - if m.UpdateTaskOptions == nil { - m.UpdateTaskOptions = &opt - } - - return m.ResTask, m.UpdateError -} - -func (m *MockKapa) DeleteTask(link client.Link) error { - m.Link = link - return m.DeleteError -} - -type MockID struct { - ID string -} - -func (m *MockID) Generate() (string, error) { - return m.ID, nil -} - -func TestClient_All(t *testing.T) { - type fields struct { - URL string - Username string - Password string - ID chronograf.ID - Ticker chronograf.Ticker - kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) - } - type args struct { - ctx context.Context - } - kapa := &MockKapa{} - tests := []struct { - name string - fields fields - args args - want map[string]*Task - wantErr bool - resTask client.Task - resTasks []client.Task - resError error - - createTaskOptions client.CreateTaskOptions - link client.Link - taskOptions *client.TaskOptions - listTasksOptions *client.ListTasksOptions - updateTaskOptions client.UpdateTaskOptions - }{ - { - name: "return no tasks", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - }, - listTasksOptions: &client.ListTasksOptions{}, - want: map[string]*Task{}, - }, - { - name: "return a non-reversible task", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - }, - listTasksOptions: &client.ListTasksOptions{}, - resTasks: []client.Task{ - client.Task{ - ID: "howdy", - Status: client.Enabled, - }, - }, - want: map[string]*Task{ - "howdy": &Task{ - ID: "howdy", - - HrefOutput: "/kapacitor/v1/tasks/howdy/output", - Rule: chronograf.AlertRule{ - ID: "howdy", - Name: "howdy", - TICKScript: "", - Type: "invalid", - Status: "enabled", - DBRPs: []chronograf.DBRP{}, - }, - TICKScript: "", - }, - }, - }, - { - name: "return a reversible task", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - }, - listTasksOptions: &client.ListTasksOptions{}, - resTasks: []client.Task{ - client.Task{ - ID: "rule 1", - Status: client.Enabled, - Type: client.StreamTask, - DBRPs: []client.DBRP{ - { - Database: "_internal", - RetentionPolicy: "autogen", - }, - }, - TICKscript: `var db = '_internal' - -var rp = 'monitor' - -var measurement = 'cq' - -var groupBy = [] - -var whereFilter = lambda: TRUE - -var name = 'rule 1' - -var idVar = name + ':{{.Group}}' - -var message = '' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var crit = 90000 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |eval(lambda: "queryOk") - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - }, - }, - want: map[string]*Task{ - "rule 1": &Task{ - ID: "rule 1", - - HrefOutput: "/kapacitor/v1/tasks/rule 1/output", - Rule: chronograf.AlertRule{ - DBRPs: []chronograf.DBRP{ - { - - DB: "_internal", - RP: "autogen", - }, - }, - Type: "stream", - Status: "enabled", - ID: "rule 1", - Name: "rule 1", - TICKScript: `var db = '_internal' - -var rp = 'monitor' - -var measurement = 'cq' - -var groupBy = [] - -var whereFilter = lambda: TRUE - -var name = 'rule 1' - -var idVar = name + ':{{.Group}}' - -var message = '' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var crit = 90000 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |eval(lambda: "queryOk") - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - Trigger: "threshold", - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90000", - }, - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - }, - Query: &chronograf.QueryConfig{ - Database: "_internal", - RetentionPolicy: "monitor", - Measurement: "cq", - Fields: []chronograf.Field{ - { - Value: "queryOk", - Type: "field", - }, - }, - GroupBy: chronograf.GroupBy{ - Tags: []string{}, - }, - AreTagsAccepted: false, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - kapa.ResTask = tt.resTask - kapa.ResTasks = tt.resTasks - kapa.ListError = tt.resError - t.Run(tt.name, func(t *testing.T) { - c := &Client{ - URL: tt.fields.URL, - Username: tt.fields.Username, - Password: tt.fields.Password, - ID: tt.fields.ID, - Ticker: tt.fields.Ticker, - kapaClient: tt.fields.kapaClient, - } - got, err := c.All(tt.args.ctx) - if (err != nil) != tt.wantErr { - t.Errorf("Client.All() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !cmp.Equal(got, tt.want) { - t.Errorf("%q. Client.All() = -got/+want %s", tt.name, cmp.Diff(got, tt.want)) - } - if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) { - t.Errorf("Client.All() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions) - } - if !reflect.DeepEqual(kapa.TaskOptions, tt.taskOptions) { - t.Errorf("Client.All() = taskOptions %v, want %v", kapa.TaskOptions, tt.taskOptions) - } - if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) { - t.Errorf("Client.All() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions) - } - if !reflect.DeepEqual(kapa.Link, tt.link) { - t.Errorf("Client.All() = Link %v, want %v", kapa.Link, tt.link) - } - }) - } -} - -func TestClient_Get(t *testing.T) { - type fields struct { - URL string - Username string - Password string - ID chronograf.ID - Ticker chronograf.Ticker - kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) - } - type args struct { - ctx context.Context - id string - } - kapa := &MockKapa{} - tests := []struct { - name string - fields fields - args args - want *Task - wantErr bool - resTask client.Task - resTasks []client.Task - resError error - - createTaskOptions client.CreateTaskOptions - link client.Link - taskOptions *client.TaskOptions - listTasksOptions *client.ListTasksOptions - updateTaskOptions client.UpdateTaskOptions - }{ - { - name: "return no task", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - }, - args: args{ - id: "myid", - }, - taskOptions: nil, - wantErr: true, - resError: fmt.Errorf("no such task"), - link: client.Link{ - Href: "/kapacitor/v1/tasks/myid", - }, - }, - { - name: "return non-reversible task", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - }, - args: args{ - id: "myid", - }, - taskOptions: nil, - resTask: client.Task{ - ID: "myid", - Status: client.Enabled, - Type: client.StreamTask, - DBRPs: []client.DBRP{ - { - Database: "_internal", - RetentionPolicy: "autogen", - }, - }, - }, - want: &Task{ - ID: "myid", - HrefOutput: "/kapacitor/v1/tasks/myid/output", - Rule: chronograf.AlertRule{ - Type: "stream", - Status: "enabled", - ID: "myid", - Name: "myid", - DBRPs: []chronograf.DBRP{ - { - DB: "_internal", - RP: "autogen", - }, - }, - }, - }, - link: client.Link{ - Href: "/kapacitor/v1/tasks/myid", - }, - }, - { - name: "return reversible task", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - }, - args: args{ - id: "rule 1", - }, - taskOptions: nil, - resTask: client.Task{ - ID: "rule 1", - Status: client.Enabled, - Type: client.StreamTask, - DBRPs: []client.DBRP{ - { - Database: "_internal", - RetentionPolicy: "autogen", - }, - }, - TICKscript: `var db = '_internal' - -var rp = 'monitor' - -var measurement = 'cq' - -var groupBy = [] - -var whereFilter = lambda: TRUE - -var name = 'rule 1' - -var idVar = name + ':{{.Group}}' - -var message = '' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var crit = 90000 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |eval(lambda: "queryOk") - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - }, - want: &Task{ - ID: "rule 1", - HrefOutput: "/kapacitor/v1/tasks/rule 1/output", - Rule: chronograf.AlertRule{ - Type: "stream", - Status: "enabled", - DBRPs: []chronograf.DBRP{ - { - - DB: "_internal", - RP: "autogen", - }, - }, - ID: "rule 1", - Name: "rule 1", - TICKScript: `var db = '_internal' - -var rp = 'monitor' - -var measurement = 'cq' - -var groupBy = [] - -var whereFilter = lambda: TRUE - -var name = 'rule 1' - -var idVar = name + ':{{.Group}}' - -var message = '' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var crit = 90000 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |eval(lambda: "queryOk") - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - Trigger: "threshold", - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90000", - }, - AlertNodes: chronograf.AlertNodes{ - IsStateChangesOnly: true, - }, - Query: &chronograf.QueryConfig{ - Database: "_internal", - RetentionPolicy: "monitor", - Measurement: "cq", - Fields: []chronograf.Field{ - { - Value: "queryOk", - Type: "field", - }, - }, - GroupBy: chronograf.GroupBy{ - Tags: []string{}, - }, - AreTagsAccepted: false, - }, - }, - }, - link: client.Link{ - Href: "/kapacitor/v1/tasks/rule 1", - }, - }, - } - for _, tt := range tests { - kapa.ResTask = tt.resTask - kapa.ResTasks = tt.resTasks - kapa.TaskError = tt.resError - t.Run(tt.name, func(t *testing.T) { - c := &Client{ - URL: tt.fields.URL, - Username: tt.fields.Username, - Password: tt.fields.Password, - ID: tt.fields.ID, - Ticker: tt.fields.Ticker, - kapaClient: tt.fields.kapaClient, - } - got, err := c.Get(tt.args.ctx, tt.args.id) - if (err != nil) != tt.wantErr { - t.Errorf("Client.Get() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !cmp.Equal(got, tt.want) { - t.Errorf("%q. Client.All() = -got/+want %s", tt.name, cmp.Diff(got, tt.want)) - } - if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) { - t.Errorf("Client.Get() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions) - } - if !reflect.DeepEqual(kapa.TaskOptions, tt.taskOptions) { - t.Errorf("Client.Get() = taskOptions %v, want %v", kapa.TaskOptions, tt.taskOptions) - } - if !reflect.DeepEqual(kapa.ListTasksOptions, tt.listTasksOptions) { - t.Errorf("Client.Get() = listTasksOptions %v, want %v", kapa.ListTasksOptions, tt.listTasksOptions) - } - if !reflect.DeepEqual(kapa.Link, tt.link) { - t.Errorf("Client.Get() = Link %v, want %v", kapa.Link, tt.link) - } - }) - } -} - -func TestClient_updateStatus(t *testing.T) { - type fields struct { - URL string - Username string - Password string - ID chronograf.ID - Ticker chronograf.Ticker - kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) - } - type args struct { - ctx context.Context - href string - status client.TaskStatus - } - kapa := &MockKapa{} - tests := []struct { - name string - fields fields - args args - resTask client.Task - want *Task - resError error - wantErr bool - updateTaskOptions *client.UpdateTaskOptions - }{ - { - name: "disable alert rule", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/howdy", - status: client.Disabled, - }, - resTask: client.Task{ - ID: "howdy", - Status: client.Disabled, - Type: client.StreamTask, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - Link: client.Link{ - Href: "/kapacitor/v1/tasks/howdy", - }, - }, - updateTaskOptions: &client.UpdateTaskOptions{ - TICKscript: "", - Status: client.Disabled, - }, - want: &Task{ - ID: "howdy", - Href: "/kapacitor/v1/tasks/howdy", - HrefOutput: "/kapacitor/v1/tasks/howdy/output", - Rule: chronograf.AlertRule{ - ID: "howdy", - Name: "howdy", - Type: "stream", - DBRPs: []chronograf.DBRP{ - { - - DB: "db", - RP: "rp", - }, - }, - Status: "disabled", - }, - }, - }, - { - name: "fail to enable alert rule", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/howdy", - status: client.Enabled, - }, - updateTaskOptions: &client.UpdateTaskOptions{ - TICKscript: "", - Status: client.Enabled, - }, - resError: fmt.Errorf("error"), - wantErr: true, - }, - { - name: "enable alert rule", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/howdy", - status: client.Enabled, - }, - resTask: client.Task{ - ID: "howdy", - Type: client.StreamTask, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - Status: client.Enabled, - Link: client.Link{ - Href: "/kapacitor/v1/tasks/howdy", - }, - }, - updateTaskOptions: &client.UpdateTaskOptions{ - TICKscript: "", - Status: client.Enabled, - }, - want: &Task{ - ID: "howdy", - Href: "/kapacitor/v1/tasks/howdy", - HrefOutput: "/kapacitor/v1/tasks/howdy/output", - Rule: chronograf.AlertRule{ - ID: "howdy", - Name: "howdy", - Type: "stream", - DBRPs: []chronograf.DBRP{ - { - - DB: "db", - RP: "rp", - }, - }, - Status: "enabled", - }, - }, - }, - } - for _, tt := range tests { - kapa.ResTask = tt.resTask - kapa.UpdateError = tt.resError - kapa.UpdateTaskOptions = nil - t.Run(tt.name, func(t *testing.T) { - c := &Client{ - URL: tt.fields.URL, - Username: tt.fields.Username, - Password: tt.fields.Password, - ID: tt.fields.ID, - Ticker: tt.fields.Ticker, - kapaClient: tt.fields.kapaClient, - } - got, err := c.updateStatus(tt.args.ctx, tt.args.href, tt.args.status) - if (err != nil) != tt.wantErr { - t.Errorf("Client.updateStatus() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !cmp.Equal(got, tt.want) { - t.Errorf("%q. Client.updateStatus() = -got/+want %s", tt.name, cmp.Diff(got, tt.want)) - } - if !reflect.DeepEqual(kapa.UpdateTaskOptions, tt.updateTaskOptions) { - t.Errorf("Client.updateStatus() = %v, want %v", kapa.UpdateTaskOptions, tt.updateTaskOptions) - } - }) - } -} - -func TestClient_Update(t *testing.T) { - type fields struct { - URL string - Username string - Password string - ID chronograf.ID - Ticker chronograf.Ticker - kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) - } - type args struct { - ctx context.Context - href string - rule chronograf.AlertRule - } - kapa := &MockKapa{} - tests := []struct { - name string - fields fields - args args - resTask client.Task - want *Task - resError error - wantErr bool - updateTaskOptions *client.UpdateTaskOptions - wantStatus client.TaskStatus - }{ - { - name: "update alert rule error", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/howdy", - rule: chronograf.AlertRule{ - ID: "howdy", - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - }, - }, - }, - resError: fmt.Errorf("error"), - updateTaskOptions: &client.UpdateTaskOptions{ - TICKscript: "", - Type: client.StreamTask, - Status: client.Disabled, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - }, - wantErr: true, - wantStatus: client.Disabled, - }, - { - name: "update alert rule", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/howdy", - rule: chronograf.AlertRule{ - ID: "howdy", - Name: "myname", - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - Measurement: "meas", - Fields: []chronograf.Field{ - { - Type: "field", - Value: "usage_user", - }, - }, - }, - Trigger: "threshold", - TriggerValues: chronograf.TriggerValues{ - Operator: greaterThan, - }, - }, - }, - resTask: client.Task{ - ID: "howdy", - Type: client.StreamTask, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - Status: client.Enabled, - Link: client.Link{ - Href: "/kapacitor/v1/tasks/howdy", - }, - }, - updateTaskOptions: &client.UpdateTaskOptions{ - TICKscript: "", - Type: client.StreamTask, - Status: client.Disabled, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - }, - want: &Task{ - ID: "howdy", - Href: "/kapacitor/v1/tasks/howdy", - HrefOutput: "/kapacitor/v1/tasks/howdy/output", - Rule: chronograf.AlertRule{ - DBRPs: []chronograf.DBRP{ - { - - DB: "db", - RP: "rp", - }, - }, - Status: "enabled", - Type: "stream", - ID: "howdy", - Name: "howdy", - }, - }, - wantStatus: client.Enabled, - }, - { - name: "stays disabled when already disabled", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/howdy", - rule: chronograf.AlertRule{ - ID: "howdy", - Name: "myname", - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - Measurement: "meas", - Fields: []chronograf.Field{ - { - Type: "field", - Value: "usage_user", - }, - }, - }, - Trigger: "threshold", - TriggerValues: chronograf.TriggerValues{ - Operator: greaterThan, - }, - }, - }, - resTask: client.Task{ - ID: "howdy", - Type: client.StreamTask, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - Status: client.Disabled, - Link: client.Link{ - Href: "/kapacitor/v1/tasks/howdy", - }, - }, - updateTaskOptions: &client.UpdateTaskOptions{ - TICKscript: "", - Type: client.StreamTask, - Status: client.Disabled, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - }, - want: &Task{ - ID: "howdy", - Href: "/kapacitor/v1/tasks/howdy", - HrefOutput: "/kapacitor/v1/tasks/howdy/output", - Rule: chronograf.AlertRule{ - ID: "howdy", - Name: "howdy", - DBRPs: []chronograf.DBRP{ - { - - DB: "db", - RP: "rp", - }, - }, - Status: "disabled", - Type: "stream", - }, - }, - wantStatus: client.Disabled, - }, - { - name: "error because relative cannot have inside range", - wantErr: true, - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/error", - rule: chronograf.AlertRule{ - ID: "error", - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - Fields: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - Trigger: Relative, - TriggerValues: chronograf.TriggerValues{ - Operator: insideRange, - }, - }, - }, - }, - { - name: "error because rule has an unknown trigger mechanism", - wantErr: true, - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/error", - rule: chronograf.AlertRule{ - ID: "error", - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - }, - }, - }, - }, - { - name: "error because query has no fields", - wantErr: true, - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/error", - rule: chronograf.AlertRule{ - ID: "error", - Trigger: Threshold, - TriggerValues: chronograf.TriggerValues{ - Period: "1d", - }, - Name: "myname", - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - Measurement: "meas", - }, - }, - }, - }, - { - name: "error because alert has no name", - wantErr: true, - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/error", - rule: chronograf.AlertRule{ - ID: "error", - Trigger: Deadman, - TriggerValues: chronograf.TriggerValues{ - Period: "1d", - }, - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - Measurement: "meas", - }, - }, - }, - }, - { - name: "error because alert period cannot be an empty string in deadman alert", - wantErr: true, - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - }, - args: args{ - ctx: context.Background(), - href: "/kapacitor/v1/tasks/error", - rule: chronograf.AlertRule{ - ID: "error", - Name: "myname", - Trigger: Deadman, - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - Measurement: "meas", - }, - }, - }, - }, - } - for _, tt := range tests { - kapa.ResTask = tt.resTask - kapa.UpdateError = tt.resError - t.Run(tt.name, func(t *testing.T) { - c := &Client{ - URL: tt.fields.URL, - Username: tt.fields.Username, - Password: tt.fields.Password, - ID: tt.fields.ID, - Ticker: tt.fields.Ticker, - kapaClient: tt.fields.kapaClient, - } - got, err := c.Update(tt.args.ctx, tt.args.href, tt.args.rule) - if (err != nil) != tt.wantErr { - t.Errorf("Client.Update() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr { - return - } - if !cmp.Equal(got, tt.want) { - t.Errorf("%q. Client.Update() = -got/+want %s", tt.name, cmp.Diff(got, tt.want)) - } - var cmpOptions = cmp.Options{ - cmpopts.IgnoreFields(client.UpdateTaskOptions{}, "TICKscript"), - } - if !cmp.Equal(kapa.UpdateTaskOptions, tt.updateTaskOptions, cmpOptions...) { - t.Errorf("Client.Update() = %s", cmp.Diff(got, tt.updateTaskOptions, cmpOptions...)) - } - if tt.wantStatus != kapa.LastStatus { - t.Errorf("Client.Update() = %v, want %v", kapa.LastStatus, tt.wantStatus) - } - }) - } -} - -func TestClient_Create(t *testing.T) { - type fields struct { - URL string - Username string - Password string - ID chronograf.ID - Ticker chronograf.Ticker - kapaClient func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) - } - type args struct { - ctx context.Context - rule chronograf.AlertRule - } - kapa := &MockKapa{} - tests := []struct { - name string - fields fields - args args - resTask client.Task - want *Task - resError error - wantErr bool - createTaskOptions *client.CreateTaskOptions - }{ - { - name: "create alert rule with tags", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - ID: &MockID{ - ID: "howdy", - }, - }, - args: args{ - ctx: context.Background(), - rule: chronograf.AlertRule{ - ID: "", - Name: "myname's", - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - Measurement: "meas", - GroupBy: chronograf.GroupBy{ - Tags: []string{ - "tag1", - "tag2", - }, - }, - }, - Trigger: Deadman, - TriggerValues: chronograf.TriggerValues{ - Period: "1d", - }, - }, - }, - resTask: client.Task{ - ID: "chronograf-v1-howdy", - Status: client.Enabled, - Type: client.StreamTask, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - Link: client.Link{ - Href: "/kapacitor/v1/tasks/chronograf-v1-howdy", - }, - }, - createTaskOptions: &client.CreateTaskOptions{ - TICKscript: `var db = 'db' - -var rp = 'rp' - -var measurement = 'meas' - -var groupBy = ['tag1', 'tag2'] - -var whereFilter = lambda: TRUE - -var period = 1d - -var name = 'myname\'s' - -var idVar = name + ':{{.Group}}' - -var message = '' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'deadman' - -var threshold = 0.0 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - -var trigger = data - |deadman(threshold, period) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - -trigger - |eval(lambda: "emitted") - .as('value') - .keep('value', messageField, durationField) - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - - ID: "chronograf-v1-howdy", - Type: client.StreamTask, - Status: client.Enabled, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - }, - want: &Task{ - ID: "chronograf-v1-howdy", - Href: "/kapacitor/v1/tasks/chronograf-v1-howdy", - HrefOutput: "/kapacitor/v1/tasks/chronograf-v1-howdy/output", - Rule: chronograf.AlertRule{ - Type: "stream", - DBRPs: []chronograf.DBRP{ - { - - DB: "db", - RP: "rp", - }, - }, - Status: "enabled", - ID: "chronograf-v1-howdy", - Name: "chronograf-v1-howdy", - }, - }, - }, - { - name: "create alert rule with no tags", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - ID: &MockID{ - ID: "howdy", - }, - }, - args: args{ - ctx: context.Background(), - rule: chronograf.AlertRule{ - ID: "", - Name: "myname's", - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - Measurement: "meas", - }, - Trigger: Deadman, - TriggerValues: chronograf.TriggerValues{ - Period: "1d", - }, - }, - }, - resTask: client.Task{ - ID: "chronograf-v1-howdy", - Status: client.Enabled, - Type: client.StreamTask, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - Link: client.Link{ - Href: "/kapacitor/v1/tasks/chronograf-v1-howdy", - }, - }, - createTaskOptions: &client.CreateTaskOptions{ - TICKscript: `var db = 'db' - -var rp = 'rp' - -var measurement = 'meas' - -var groupBy = [] - -var whereFilter = lambda: TRUE - -var period = 1d - -var name = 'myname\'s' - -var idVar = name - -var message = '' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'deadman' - -var threshold = 0.0 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - -var trigger = data - |deadman(threshold, period) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - -trigger - |eval(lambda: "emitted") - .as('value') - .keep('value', messageField, durationField) - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - - ID: "chronograf-v1-howdy", - Type: client.StreamTask, - Status: client.Enabled, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - }, - want: &Task{ - ID: "chronograf-v1-howdy", - Href: "/kapacitor/v1/tasks/chronograf-v1-howdy", - HrefOutput: "/kapacitor/v1/tasks/chronograf-v1-howdy/output", - Rule: chronograf.AlertRule{ - Type: "stream", - DBRPs: []chronograf.DBRP{ - { - - DB: "db", - RP: "rp", - }, - }, - Status: "enabled", - ID: "chronograf-v1-howdy", - Name: "chronograf-v1-howdy", - }, - }, - }, - { - name: "create alert rule error", - fields: fields{ - kapaClient: func(url, username, password string, insecureSkipVerify bool) (KapaClient, error) { - return kapa, nil - }, - Ticker: &Alert{}, - ID: &MockID{ - ID: "howdy", - }, - }, - args: args{ - ctx: context.Background(), - rule: chronograf.AlertRule{ - ID: "howdy", - Query: &chronograf.QueryConfig{ - Database: "db", - RetentionPolicy: "rp", - }, - }, - }, - resError: fmt.Errorf("error"), - createTaskOptions: &client.CreateTaskOptions{ - ID: "chronograf-v1-howdy", - Type: client.StreamTask, - Status: client.Enabled, - DBRPs: []client.DBRP{ - { - Database: "db", - RetentionPolicy: "rp", - }, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - kapa.ResTask = tt.resTask - kapa.CreateError = tt.resError - t.Run(tt.name, func(t *testing.T) { - c := &Client{ - URL: tt.fields.URL, - Username: tt.fields.Username, - Password: tt.fields.Password, - ID: tt.fields.ID, - Ticker: tt.fields.Ticker, - kapaClient: tt.fields.kapaClient, - } - got, err := c.Create(tt.args.ctx, tt.args.rule) - if (err != nil) != tt.wantErr { - t.Errorf("Client.Create() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr { - return - } - if !cmp.Equal(got, tt.want) { - t.Errorf("%q. Client.Create() = -got/+want %s", tt.name, cmp.Diff(got, tt.want)) - } - if !reflect.DeepEqual(kapa.CreateTaskOptions, tt.createTaskOptions) { - t.Errorf("Client.Create() = %v, want %v", kapa.CreateTaskOptions, tt.createTaskOptions) - } - }) - } -} diff --git a/chronograf/.kapacitor/data.go b/chronograf/.kapacitor/data.go deleted file mode 100644 index a8dc218ca24..00000000000 --- a/chronograf/.kapacitor/data.go +++ /dev/null @@ -1,63 +0,0 @@ -package kapacitor - -import ( - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Data returns the tickscript data section for querying -func Data(rule chronograf.AlertRule) (string, error) { - if rule.Query.RawText != nil && *rule.Query.RawText != "" { - batch := ` - var data = batch - |query(''' - %s - ''') - .period(period) - .every(every) - .align()` - batch = fmt.Sprintf(batch, rule.Query.RawText) - if rule.Query.GroupBy.Time != "" { - batch = batch + fmt.Sprintf(".groupBy(%s)", rule.Query.GroupBy.Time) - } - return batch, nil - } - stream := `var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - ` - - stream = fmt.Sprintf("%s\n.groupBy(groupBy)\n", stream) - stream = stream + ".where(whereFilter)\n" - // Only need aggregate functions for threshold and relative - - if rule.Trigger != "deadman" { - fld, err := field(rule.Query) - if err != nil { - return "", err - } - value := "" - for _, field := range rule.Query.Fields { - if field.Type == "func" && len(field.Args) > 0 && field.Args[0].Type == "field" { - // Only need a window if we have an aggregate function - value = value + "|window().period(period).every(every).align()\n" - value = value + fmt.Sprintf(`|%s('%s').as('value')`, field.Value, field.Args[0].Value) - break // only support a single field - } - if value != "" { - break // only support a single field - } - if field.Type == "field" { - value = fmt.Sprintf(`|eval(lambda: "%s").as('value')`, field.Value) - } - } - if value == "" { - value = fmt.Sprintf(`|eval(lambda: "%s").as('value')`, fld) - } - stream = stream + value - } - return stream, nil -} diff --git a/chronograf/.kapacitor/data_test.go b/chronograf/.kapacitor/data_test.go deleted file mode 100644 index 35a4544e4a3..00000000000 --- a/chronograf/.kapacitor/data_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package kapacitor - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var config = `{ - "id": "93e17825-2fb0-4507-87bd-a0c136947f7e", - "database": "telegraf", - "measurement": "cpu", - "retentionPolicy": "default", - "fields": [{ - "field": "usage_user", - "funcs": ["mean"] - }], - "tags": { - "host": [ - "acc-0eabc309-eu-west-1-data-3", - "prod" - ], - "cpu": [ - "cpu_total" - ] - }, - "groupBy": { - "time": null, - "tags": [ - "host", - "cluster_id" - ] - }, - "areTagsAccepted": true, - "rawText": null -}` - -func TestData(t *testing.T) { - q := chronograf.QueryConfig{} - err := json.Unmarshal([]byte(config), &q) - if err != nil { - t.Errorf("Error unmarshalling %v", err) - } - alert := chronograf.AlertRule{ - Trigger: "deadman", - Query: &q, - } - if tick, err := Data(alert); err != nil { - t.Errorf("Error creating tick %v", err) - } else { - _, err := formatTick(tick) - if err != nil { - fmt.Print(tick) - t.Errorf("Error formatting tick %v", err) - } - } - -} diff --git a/chronograf/.kapacitor/errors.go b/chronograf/.kapacitor/errors.go deleted file mode 100644 index e57cd839b93..00000000000 --- a/chronograf/.kapacitor/errors.go +++ /dev/null @@ -1,12 +0,0 @@ -package kapacitor - -// ErrNotChronoTickscript signals a TICKscript that cannot be parsed into -// chronograf data structure. -const ErrNotChronoTickscript = Error("TICKscript not built with chronograf builder") - -// Error are kapacitor errors due to communication or processing of TICKscript to kapacitor -type Error string - -func (e Error) Error() string { - return string(e) -} diff --git a/chronograf/.kapacitor/http_out.go b/chronograf/.kapacitor/http_out.go deleted file mode 100644 index ec569144d6c..00000000000 --- a/chronograf/.kapacitor/http_out.go +++ /dev/null @@ -1,15 +0,0 @@ -package kapacitor - -import ( - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// HTTPEndpoint is the default location of the tickscript output -const HTTPEndpoint = "output" - -// HTTPOut adds a kapacitor httpOutput to a tickscript -func HTTPOut(rule chronograf.AlertRule) (string, error) { - return fmt.Sprintf(`trigger|httpOut('%s')`, HTTPEndpoint), nil -} diff --git a/chronograf/.kapacitor/influxout.go b/chronograf/.kapacitor/influxout.go deleted file mode 100644 index 8cf507a4473..00000000000 --- a/chronograf/.kapacitor/influxout.go +++ /dev/null @@ -1,34 +0,0 @@ -package kapacitor - -import ( - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// InfluxOut creates a kapacitor influxDBOut node to write alert data to Database, RP, Measurement. -func InfluxOut(rule chronograf.AlertRule) (string, error) { - // For some of the alert, the data needs to be renamed (normalized) - // before being sent to influxdb. - - rename := "" - if rule.Trigger == "deadman" { - rename = `|eval(lambda: "emitted") - .as('value') - .keep('value', messageField, durationField)` - } - return fmt.Sprintf(` - trigger - %s - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - `, rename), nil -} diff --git a/chronograf/.kapacitor/influxout_test.go b/chronograf/.kapacitor/influxout_test.go deleted file mode 100644 index faeef743926..00000000000 --- a/chronograf/.kapacitor/influxout_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package kapacitor - -import "testing" -import "github.com/influxdata/influxdb/v2/chronograf" - -func TestInfluxOut(t *testing.T) { - tests := []struct { - name string - want chronograf.TICKScript - }{ - { - name: "Test influxDBOut kapacitor node", - want: `trigger - |eval(lambda: "emitted") - .as('value') - .keep('value', messageField, durationField) - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) -`, - }, - } - for _, tt := range tests { - got, err := InfluxOut(chronograf.AlertRule{ - Name: "name", - Trigger: "deadman", - Query: &chronograf.QueryConfig{ - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - }, - }) - if err != nil { - t.Errorf("%q. InfluxOut()) error = %v", tt.name, err) - continue - } - formatted, err := formatTick(got) - if err != nil { - t.Errorf("%q. formatTick() error = %v", tt.name, err) - continue - } - if formatted != tt.want { - t.Errorf("%q. InfluxOut() = %v, want %v", tt.name, formatted, tt.want) - } - } -} diff --git a/chronograf/.kapacitor/kapa_client.go b/chronograf/.kapacitor/kapa_client.go deleted file mode 100644 index 7d5db8a2969..00000000000 --- a/chronograf/.kapacitor/kapa_client.go +++ /dev/null @@ -1,113 +0,0 @@ -package kapacitor - -import ( - "sync" - - client "github.com/influxdata/kapacitor/client/v1" -) - -const ( - // ListTaskWorkers describes the number of workers concurrently fetching - // tasks from Kapacitor. This constant was chosen after some benchmarking - // work and should likely work well for quad-core systems - ListTaskWorkers = 4 - - // TaskGatherers is the number of workers collating responses from - // ListTaskWorkers. There can only be one without additional synchronization - // around the output buffer from ListTasks - TaskGatherers = 1 -) - -// ensure PaginatingKapaClient is a KapaClient -var _ KapaClient = &PaginatingKapaClient{} - -// PaginatingKapaClient is a Kapacitor client that automatically navigates -// through Kapacitor's pagination to fetch all results -type PaginatingKapaClient struct { - KapaClient - FetchRate int // specifies the number of elements to fetch from Kapacitor at a time -} - -// ListTasks lists all available tasks from Kapacitor, navigating pagination as -// it fetches them -func (p *PaginatingKapaClient) ListTasks(opts *client.ListTasksOptions) ([]client.Task, error) { - // only trigger auto-pagination with Offset=0 and Limit=0 - if opts.Limit != 0 || opts.Offset != 0 { - return p.KapaClient.ListTasks(opts) - } - - allTasks := []client.Task{} - - optChan := make(chan client.ListTasksOptions) - taskChan := make(chan []client.Task, ListTaskWorkers) - done := make(chan struct{}) - - var once sync.Once - - go p.generateKapacitorOptions(optChan, *opts, done) - - var wg sync.WaitGroup - - wg.Add(ListTaskWorkers) - for i := 0; i < ListTaskWorkers; i++ { - go p.fetchFromKapacitor(optChan, &wg, &once, taskChan, done) - } - - var gatherWg sync.WaitGroup - gatherWg.Add(TaskGatherers) - go func() { - for task := range taskChan { - allTasks = append(allTasks, task...) - } - gatherWg.Done() - }() - - wg.Wait() - close(taskChan) - gatherWg.Wait() - - return allTasks, nil -} - -// fetchFromKapacitor fetches a set of results from a kapacitor by reading a -// set of options from the provided optChan. Fetched tasks are pushed onto the -// provided taskChan -func (p *PaginatingKapaClient) fetchFromKapacitor(optChan chan client.ListTasksOptions, wg *sync.WaitGroup, closer *sync.Once, taskChan chan []client.Task, done chan struct{}) { - defer wg.Done() - for opt := range optChan { - resp, err := p.KapaClient.ListTasks(&opt) - if err != nil { - return - } - - // break and stop all workers if we're done - if len(resp) == 0 { - closer.Do(func() { - close(done) - }) - return - } - - // handoff tasks to consumer - taskChan <- resp - } -} - -// generateKapacitorOptions creates ListTasksOptions with incrementally greater -// Limit and Offset parameters, and inserts them into the provided optChan -func (p *PaginatingKapaClient) generateKapacitorOptions(optChan chan client.ListTasksOptions, opts client.ListTasksOptions, done chan struct{}) { - // ensure Limit and Offset start from known quantities - opts.Limit = p.FetchRate - opts.Offset = 0 - - for { - select { - case <-done: - close(optChan) - return - case optChan <- opts: - // nop - } - opts.Offset = p.FetchRate + opts.Offset - } -} diff --git a/chronograf/.kapacitor/kapa_client_benchmark_test.go b/chronograf/.kapacitor/kapa_client_benchmark_test.go deleted file mode 100644 index 0d2dc08d965..00000000000 --- a/chronograf/.kapacitor/kapa_client_benchmark_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package kapacitor_test - -import ( - "testing" - - "github.com/influxdata/influxdb/v2/chronograf/kapacitor" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - client "github.com/influxdata/kapacitor/client/v1" -) - -func BenchmarkKapaClient100(b *testing.B) { benchmark_PaginatingKapaClient(100, b) } -func BenchmarkKapaClient1000(b *testing.B) { benchmark_PaginatingKapaClient(1000, b) } -func BenchmarkKapaClient10000(b *testing.B) { benchmark_PaginatingKapaClient(10000, b) } -func BenchmarkKapaClient100000(b *testing.B) { benchmark_PaginatingKapaClient(100000, b) } - -var tasks []client.Task - -func benchmark_PaginatingKapaClient(taskCount int, b *testing.B) { - - b.StopTimer() // eliminate setup time - - // create a mock client that will return a huge response from ListTasks - mockClient := &mocks.KapaClient{ - ListTasksF: func(opts *client.ListTasksOptions) ([]client.Task, error) { - // create all the tasks - allTasks := make([]client.Task, taskCount) - - begin := opts.Offset - end := opts.Offset + opts.Limit - - if end > len(allTasks) { - end = len(allTasks) - } - - if begin > len(allTasks) { - begin = end - } - - return allTasks[begin:end], nil - }, - } - - pkap := kapacitor.PaginatingKapaClient{ - KapaClient: mockClient, - FetchRate: 50, - } - - opts := &client.ListTasksOptions{} - - b.StartTimer() // eliminate setup time - - // let the benchmark runner run ListTasks until it's satisfied - for n := 0; n < b.N; n++ { - // assignment is to avoid having the call optimized away - tasks, _ = pkap.ListTasks(opts) - } -} diff --git a/chronograf/.kapacitor/kapa_client_test.go b/chronograf/.kapacitor/kapa_client_test.go deleted file mode 100644 index c9e5ce5220b..00000000000 --- a/chronograf/.kapacitor/kapa_client_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package kapacitor_test - -import ( - "testing" - - "github.com/influxdata/influxdb/v2/chronograf/kapacitor" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - client "github.com/influxdata/kapacitor/client/v1" -) - -func Test_Kapacitor_PaginatingKapaClient(t *testing.T) { - const lenAllTasks = 227 // prime, to stress odd result sets - - // create a mock client that will return a huge response from ListTasks - mockClient := &mocks.KapaClient{ - ListTasksF: func(opts *client.ListTasksOptions) ([]client.Task, error) { - // create all the tasks - allTasks := []client.Task{} - for i := 0; i < lenAllTasks; i++ { - allTasks = append(allTasks, client.Task{}) - } - begin := opts.Offset - end := opts.Offset + opts.Limit - - if end > len(allTasks) { - end = len(allTasks) - } - - if begin > len(allTasks) { - begin = end - } - - return allTasks[begin:end], nil - }, - } - - pkap := kapacitor.PaginatingKapaClient{ - KapaClient: mockClient, - FetchRate: 50, - } - - opts := &client.ListTasksOptions{ - Limit: 100, - Offset: 0, - } - - // ensure 100 elems returned when calling mockClient directly - tasks, _ := pkap.ListTasks(opts) - - if len(tasks) != 100 { - t.Error("Expected calling KapaClient's ListTasks to return", opts.Limit, "items. Received:", len(tasks)) - } - - // ensure PaginatingKapaClient returns _all_ tasks with 0 value for Limit and Offset - allOpts := &client.ListTasksOptions{} - allTasks, _ := pkap.ListTasks(allOpts) - - if len(allTasks) != lenAllTasks { - t.Error("PaginatingKapaClient: Expected to find", lenAllTasks, "tasks but found", len(allTasks)) - } -} diff --git a/chronograf/.kapacitor/operators.go b/chronograf/.kapacitor/operators.go deleted file mode 100644 index 5b53a1d2038..00000000000 --- a/chronograf/.kapacitor/operators.go +++ /dev/null @@ -1,78 +0,0 @@ -package kapacitor - -import ( - "fmt" -) - -const ( - greaterThan = "greater than" - lessThan = "less than" - lessThanEqual = "equal to or less than" - greaterThanEqual = "equal to or greater" - equal = "equal to" - notEqual = "not equal to" - insideRange = "inside range" - outsideRange = "outside range" -) - -// kapaOperator converts UI strings to kapacitor operators -func kapaOperator(operator string) (string, error) { - switch operator { - case greaterThan: - return ">", nil - case lessThan: - return "<", nil - case lessThanEqual: - return "<=", nil - case greaterThanEqual: - return ">=", nil - case equal: - return "==", nil - case notEqual: - return "!=", nil - default: - return "", fmt.Errorf("invalid operator: %s is unknown", operator) - } -} - -func chronoOperator(operator string) (string, error) { - switch operator { - case ">": - return greaterThan, nil - case "<": - return lessThan, nil - case "<=": - return lessThanEqual, nil - case ">=": - return greaterThanEqual, nil - case "==": - return equal, nil - case "!=": - return notEqual, nil - default: - return "", fmt.Errorf("invalid operator: %s is unknown", operator) - } -} - -func rangeOperators(operator string) ([]string, error) { - switch operator { - case insideRange: - return []string{">=", "AND", "<="}, nil - case outsideRange: - return []string{"<", "OR", ">"}, nil - default: - return nil, fmt.Errorf("invalid operator: %s is unknown", operator) - } -} - -func chronoRangeOperators(ops []string) (string, error) { - if len(ops) != 3 { - return "", fmt.Errorf("unknown operators") - } - if ops[0] == ">=" && ops[1] == "AND" && ops[2] == "<=" { - return insideRange, nil - } else if ops[0] == "<" && ops[1] == "OR" && ops[2] == ">" { - return outsideRange, nil - } - return "", fmt.Errorf("unknown operators") -} diff --git a/chronograf/.kapacitor/pipeline.go b/chronograf/.kapacitor/pipeline.go deleted file mode 100644 index a17db1f5c94..00000000000 --- a/chronograf/.kapacitor/pipeline.go +++ /dev/null @@ -1,37 +0,0 @@ -package kapacitor - -import ( - "bytes" - "encoding/json" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/kapacitor/pipeline" - totick "github.com/influxdata/kapacitor/pipeline/tick" -) - -// MarshalTICK converts tickscript to JSON representation -func MarshalTICK(script string) ([]byte, error) { - pipeline, err := newPipeline(chronograf.TICKScript(script)) - if err != nil { - return nil, err - } - return json.MarshalIndent(pipeline, "", " ") -} - -// UnmarshalTICK converts JSON to tickscript -func UnmarshalTICK(octets []byte) (string, error) { - pipe := &pipeline.Pipeline{} - if err := pipe.Unmarshal(octets); err != nil { - return "", err - } - - ast := totick.AST{} - err := ast.Build(pipe) - if err != nil { - return "", err - } - - var buf bytes.Buffer - ast.Program.Format(&buf, "", false) - return buf.String(), nil -} diff --git a/chronograf/.kapacitor/pipeline_test.go b/chronograf/.kapacitor/pipeline_test.go deleted file mode 100644 index 6c8eb3367be..00000000000 --- a/chronograf/.kapacitor/pipeline_test.go +++ /dev/null @@ -1,341 +0,0 @@ -package kapacitor - -import ( - "fmt" - "testing" - - "github.com/sergi/go-diff/diffmatchpatch" -) - -func TestPipelineJSON(t *testing.T) { - script := `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var every = 30s - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var crit = 90 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email() - -trigger - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -` - - want := `var alert4 = stream - |from() - .database('telegraf') - .retentionPolicy('autogen') - .measurement('cpu') - .where(lambda: "cpu" == 'cpu_total' AND "host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - .groupBy('host', 'cluster_id') - |window() - .period(10m) - .every(30s) - .align() - |mean('usage_user') - .as('value') - |alert() - .id('name:{{.Group}}') - .message('message') - .details('{{ json . }}') - .crit(lambda: "value" > 90) - .history(21) - .levelTag('level') - .messageField('message') - .durationField('duration') - .idTag('alertID') - .stateChangesOnly() - .email() - .victorOps() - .slack() - -alert4 - |httpOut('output') - -alert4 - |influxDBOut() - .database('chronograf') - .retentionPolicy('autogen') - .measurement('alerts') - .buffer(1000) - .flushInterval(10s) - .create() - .tag('alertName', 'name') - .tag('triggerType', 'threshold') -` - - octets, err := MarshalTICK(script) - if err != nil { - t.Fatalf("MarshalTICK unexpected error %v", err) - } - - got, err := UnmarshalTICK(octets) - if err != nil { - t.Fatalf("UnmarshalTICK unexpected error %v", err) - } - - if got != want { - fmt.Println(got) - diff := diffmatchpatch.New() - delta := diff.DiffMain(want, got, true) - t.Errorf("%s", diff.DiffPrettyText(delta)) - } -} -func TestPipelineJSONDeadman(t *testing.T) { - script := `var db = 'telegraf' - - var rp = 'autogen' - - var measurement = 'cpu' - - var groupBy = ['host', 'cluster_id'] - - var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - - var period = 10m - - var name = 'name' - - var idVar = name + ':{{.Group}}' - - var message = 'message' - - var idTag = 'alertID' - - var levelTag = 'level' - - var messageField = 'message' - - var durationField = 'duration' - - var outputDB = 'chronograf' - - var outputRP = 'autogen' - - var outputMeasurement = 'alerts' - - var triggerType = 'deadman' - - var threshold = 0.0 - - var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - - var trigger = data - |deadman(threshold, period) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .slack() - .victorOps() - .email() - - trigger - |eval(lambda: "emitted") - .as('value') - .keep('value', messageField, durationField) - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - - trigger - |httpOut('output') -` - - wantA := `var from1 = stream - |from() - .database('telegraf') - .retentionPolicy('autogen') - .measurement('cpu') - .where(lambda: "cpu" == 'cpu_total' AND "host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - .groupBy('host', 'cluster_id') - -var alert5 = from1 - |stats(10m) - .align() - |derivative('emitted') - .as('emitted') - .unit(10m) - .nonNegative() - |alert() - .id('name:{{.Group}}') - .message('message') - .details('{{ json . }}') - .crit(lambda: "emitted" <= 0.0) - .history(21) - .levelTag('level') - .messageField('message') - .durationField('duration') - .idTag('alertID') - .stateChangesOnly() - .email() - .victorOps() - .slack() - -alert5 - |httpOut('output') - -alert5 - |eval(lambda: "emitted") - .as('value') - .tags() - .keep('value', 'message', 'duration') - |influxDBOut() - .database('chronograf') - .retentionPolicy('autogen') - .measurement('alerts') - .buffer(1000) - .flushInterval(10s) - .create() - .tag('alertName', 'name') - .tag('triggerType', 'deadman') -` - - wantB := `var from1 = stream - |from() - .database('telegraf') - .retentionPolicy('autogen') - .measurement('cpu') - .where(lambda: "cpu" == 'cpu_total' AND "host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - .groupBy('host', 'cluster_id') - -var alert5 = from1 - |stats(10m) - .align() - |derivative('emitted') - .as('emitted') - .unit(10m) - .nonNegative() - |alert() - .id('name:{{.Group}}') - .message('message') - .details('{{ json . }}') - .crit(lambda: "emitted" <= 0.0) - .history(21) - .levelTag('level') - .messageField('message') - .durationField('duration') - .idTag('alertID') - .stateChangesOnly() - .email() - .victorOps() - .slack() - -alert5 - |eval(lambda: "emitted") - .as('value') - .tags() - .keep('value', 'message', 'duration') - |influxDBOut() - .database('chronograf') - .retentionPolicy('autogen') - .measurement('alerts') - .buffer(1000) - .flushInterval(10s) - .create() - .tag('alertName', 'name') - .tag('triggerType', 'deadman') - -alert5 - |httpOut('output') -` - - octets, err := MarshalTICK(script) - if err != nil { - t.Fatalf("MarshalTICK unexpected error %v", err) - } - got, err := UnmarshalTICK(octets) - if err != nil { - t.Fatalf("UnmarshalTICK unexpected error %v", err) - } - - if got != wantA && got != wantB { - want := wantA - fmt.Println("got") - fmt.Println(got) - fmt.Println("want") - fmt.Println(want) - diff := diffmatchpatch.New() - delta := diff.DiffMain(want, got, true) - t.Errorf("%s", diff.DiffPrettyText(delta)) - } -} diff --git a/chronograf/.kapacitor/tickscripts.go b/chronograf/.kapacitor/tickscripts.go deleted file mode 100644 index d2c21d2c439..00000000000 --- a/chronograf/.kapacitor/tickscripts.go +++ /dev/null @@ -1,50 +0,0 @@ -package kapacitor - -import ( - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.Ticker = &Alert{} - -// Alert defines alerting strings in template rendering -type Alert struct{} - -// Generate creates a Tickscript from the alertrule -func (a *Alert) Generate(rule chronograf.AlertRule) (chronograf.TICKScript, error) { - vars, err := Vars(rule) - if err != nil { - return "", err - } - data, err := Data(rule) - if err != nil { - return "", err - } - trigger, err := Trigger(rule) - if err != nil { - return "", err - } - services, err := AlertServices(rule) - if err != nil { - return "", err - } - output, err := InfluxOut(rule) - if err != nil { - return "", err - } - http, err := HTTPOut(rule) - if err != nil { - return "", err - } - - raw := fmt.Sprintf("%s\n%s\n%s%s\n%s\n%s", vars, data, trigger, services, output, http) - tick, err := formatTick(raw) - if err != nil { - return "", err - } - if err := validateTick(tick); err != nil { - return tick, err - } - return tick, nil -} diff --git a/chronograf/.kapacitor/tickscripts_test.go b/chronograf/.kapacitor/tickscripts_test.go deleted file mode 100644 index 402c3d6fa47..00000000000 --- a/chronograf/.kapacitor/tickscripts_test.go +++ /dev/null @@ -1,1625 +0,0 @@ -package kapacitor - -import ( - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/sergi/go-diff/diffmatchpatch" -) - -func TestGenerate(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "relative", - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Change: "change", - Shift: "1m", - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - } - gen := Alert{} - tick, err := gen.Generate(alert) - if err != nil { - fmt.Printf("%s", tick) - t.Errorf("Error generating alert: %v %s", err, tick) - } -} - -func TestThreshold(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var every = 30s - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var crit = 90 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .email() - .victorOps() - .slack() - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - diff := diffmatchpatch.New() - delta := diff.DiffMain(string(tt.want), string(got), true) - t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) - } - } -} - -func TestThresholdStringCrit(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "haproxy", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "equal to", - Value: "DOWN", - }, - Every: "10s", - Message: `Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} `, - Details: "Email template", - Query: &chronograf.QueryConfig{ - Database: "influxdb", - RetentionPolicy: "autogen", - Measurement: "haproxy", - Fields: []chronograf.Field{ - { - Value: "last", - Type: "func", - Args: []chronograf.Field{ - { - Value: "status", - Type: "field", - }, - }, - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10s", - Tags: []string{"pxname"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'influxdb' - -var rp = 'autogen' - -var measurement = 'haproxy' - -var groupBy = ['pxname'] - -var whereFilter = lambda: TRUE - -var period = 10s - -var every = 10s - -var name = 'haproxy' - -var idVar = name + ':{{.Group}}' - -var message = 'Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} ' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var details = 'Email template' - -var crit = 'DOWN' - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |last('status') - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" == crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .details(details) - .email() - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - diff := diffmatchpatch.New() - delta := diff.DiffMain(string(tt.want), string(got), true) - t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) - } - } -} - -// TODO: Check with Nathaniel if kapacitor can do inequalities on strings -// If it cannot, I think we should add operator checks. -func TestThresholdStringCritGreater(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "haproxy", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "DOWN", - }, - Every: "10s", - Message: `Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} `, - Details: "Email template", - Query: &chronograf.QueryConfig{ - Database: "influxdb", - RetentionPolicy: "autogen", - Measurement: "haproxy", - Fields: []chronograf.Field{ - { - Value: "last", - Type: "func", - Args: []chronograf.Field{ - { - Value: "status", - Type: "field", - }, - }, - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10s", - Tags: []string{"pxname"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'influxdb' - -var rp = 'autogen' - -var measurement = 'haproxy' - -var groupBy = ['pxname'] - -var whereFilter = lambda: TRUE - -var period = 10s - -var every = 10s - -var name = 'haproxy' - -var idVar = name + ':{{.Group}}' - -var message = 'Haproxy monitor : {{.ID}} : {{ index .Tags "server" }} : {{ index .Tags "pxname" }} is {{ .Level }} ' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var details = 'Email template' - -var crit = 'DOWN' - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |last('status') - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .details(details) - .email() - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - diff := diffmatchpatch.New() - delta := diff.DiffMain(string(tt.want), string(got), true) - t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) - } - } -} - -func TestThresholdDetail(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Message: "message", - Details: "details", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var every = 30s - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var details = 'details' - -var crit = 90 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .details(details) - .email() - .victorOps() - .slack() - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - diff := diffmatchpatch.New() - delta := diff.DiffMain(string(tt.want), string(got), true) - t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) - } - } -} - -func TestThresholdInsideRange(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "inside range", - Value: "90", - RangeValue: "100", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var every = 30s - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var lower = 90 - -var upper = 100 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" >= lower AND "value" <= upper) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .email() - .victorOps() - .slack() - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - diff := diffmatchpatch.New() - delta := diff.DiffMain(string(tt.want), string(got), true) - t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) - } - } -} - -func TestThresholdOutsideRange(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "outside range", - Value: "90", - RangeValue: "100", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var every = 30s - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var lower = 90 - -var upper = 100 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" < lower OR "value" > upper) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .email() - .victorOps() - .slack() - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - diff := diffmatchpatch.New() - delta := diff.DiffMain(string(tt.want), string(got), true) - t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) - } - } -} - -func TestThresholdNoAggregate(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'threshold' - -var crit = 90 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |eval(lambda: "usage_user") - .as('value') - -var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .email() - .victorOps() - .slack() - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - diff := diffmatchpatch.New() - delta := diff.DiffMain(string(tt.want), string(got), true) - t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) - } - } -} - -func TestRelative(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "relative", - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Change: "% change", - Shift: "1m", - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var every = 30s - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'relative' - -var shift = 1m - -var crit = 90 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - -var past = data - |shift(shift) - -var current = data - -var trigger = past - |join(current) - .as('past', 'current') - |eval(lambda: abs(float("current.value" - "past.value")) / float("past.value") * 100.0) - .keep() - .as('value') - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .email() - .victorOps() - .slack() - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Relative() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - diff := diffmatchpatch.New() - delta := diff.DiffMain(string(tt.want), string(got), true) - t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) - } - } -} - -func TestRelativeChange(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "relative", - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Change: "change", - Shift: "1m", - Operator: "greater than", - Value: "90", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var every = 30s - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'relative' - -var shift = 1m - -var crit = 90 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - |window() - .period(period) - .every(every) - .align() - |mean('usage_user') - .as('value') - -var past = data - |shift(shift) - -var current = data - -var trigger = past - |join(current) - .as('past', 'current') - |eval(lambda: float("current.value" - "past.value")) - .keep() - .as('value') - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .email() - .victorOps() - .slack() - -trigger - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Relative() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - diff := diffmatchpatch.New() - delta := diff.DiffMain(string(tt.want), string(got), true) - t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta)) - } - } -} - -func TestDeadman(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "deadman", - AlertNodes: chronograf.AlertNodes{ - Slack: []*chronograf.Slack{{}}, - VictorOps: []*chronograf.VictorOps{{}}, - Email: []*chronograf.Email{{}}, - }, - TriggerValues: chronograf.TriggerValues{ - Period: "10m", - }, - Every: "30s", - Message: "message", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{ - "host": []string{ - "acc-0eabc309-eu-west-1-data-3", - "prod", - }, - "cpu": []string{ - "cpu_total", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "", - Tags: []string{"host", "cluster_id"}, - }, - AreTagsAccepted: true, - }, - } - - tests := []struct { - name string - alert chronograf.AlertRule - want chronograf.TICKScript - wantErr bool - }{ - { - name: "Test valid template alert", - alert: alert, - want: `var db = 'telegraf' - -var rp = 'autogen' - -var measurement = 'cpu' - -var groupBy = ['host', 'cluster_id'] - -var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod') - -var period = 10m - -var name = 'name' - -var idVar = name + ':{{.Group}}' - -var message = 'message' - -var idTag = 'alertID' - -var levelTag = 'level' - -var messageField = 'message' - -var durationField = 'duration' - -var outputDB = 'chronograf' - -var outputRP = 'autogen' - -var outputMeasurement = 'alerts' - -var triggerType = 'deadman' - -var threshold = 0.0 - -var data = stream - |from() - .database(db) - .retentionPolicy(rp) - .measurement(measurement) - .groupBy(groupBy) - .where(whereFilter) - -var trigger = data - |deadman(threshold, period) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) - .email() - .victorOps() - .slack() - -trigger - |eval(lambda: "emitted") - .as('value') - .keep('value', messageField, durationField) - |eval(lambda: float("value")) - .as('value') - .keep() - |influxDBOut() - .create() - .database(outputDB) - .retentionPolicy(outputRP) - .measurement(outputMeasurement) - .tag('alertName', name) - .tag('triggerType', triggerType) - -trigger - |httpOut('output') -`, - wantErr: false, - }, - } - for _, tt := range tests { - gen := Alert{} - got, err := gen.Generate(tt.alert) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Deadman() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - t.Errorf("%q\n%s", tt.name, cmp.Diff(string(tt.want), string(got))) - } - } -} diff --git a/chronograf/.kapacitor/triggers.go b/chronograf/.kapacitor/triggers.go deleted file mode 100644 index 83c92429a5e..00000000000 --- a/chronograf/.kapacitor/triggers.go +++ /dev/null @@ -1,162 +0,0 @@ -package kapacitor - -import ( - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -const ( - // Deadman triggers when data is missing for a period of time - Deadman = "deadman" - // Relative triggers when the value has changed compared to the past - Relative = "relative" - // Threshold triggers when value crosses a threshold - Threshold = "threshold" - // ThresholdRange triggers when a value is inside or outside a range - ThresholdRange = "range" - // ChangePercent triggers a relative alert when value changed by a percentage - ChangePercent = "% change" - // ChangeAmount triggers a relative alert when the value change by some amount - ChangeAmount = "change" -) - -// AllAlerts are properties all alert types will have -var AllAlerts = ` - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) -` - -// Details is used only for alerts that specify detail string -var Details = ` - .details(details) -` - -// ThresholdTrigger is the tickscript trigger for alerts that exceed a value -var ThresholdTrigger = ` - var trigger = data - |alert() - .crit(lambda: "value" %s crit) -` - -// ThresholdRangeTrigger is the alert when data does not intersect the range. -var ThresholdRangeTrigger = ` - var trigger = data - |alert() - .crit(lambda: "value" %s lower %s "value" %s upper) -` - -// RelativeAbsoluteTrigger compares one window of data versus another (current - past) -var RelativeAbsoluteTrigger = ` -var past = data - |shift(shift) - -var current = data - -var trigger = past - |join(current) - .as('past', 'current') - |eval(lambda: float("current.value" - "past.value")) - .keep() - .as('value') - |alert() - .crit(lambda: "value" %s crit) -` - -// RelativePercentTrigger compares one window of data versus another as a percent change. -var RelativePercentTrigger = ` -var past = data - |shift(shift) - -var current = data - -var trigger = past - |join(current) - .as('past', 'current') - |eval(lambda: abs(float("current.value" - "past.value"))/float("past.value") * 100.0) - .keep() - .as('value') - |alert() - .crit(lambda: "value" %s crit) -` - -// DeadmanTrigger checks if any data has been streamed in the last period of time -var DeadmanTrigger = ` - var trigger = data|deadman(threshold, period) -` - -// Trigger returns the trigger mechanism for a tickscript -func Trigger(rule chronograf.AlertRule) (string, error) { - var trigger string - var err error - switch rule.Trigger { - case Deadman: - trigger, err = DeadmanTrigger, nil - case Relative: - trigger, err = relativeTrigger(rule) - case Threshold: - if rule.TriggerValues.RangeValue == "" { - trigger, err = thresholdTrigger(rule) - } else { - trigger, err = thresholdRangeTrigger(rule) - } - default: - trigger, err = "", fmt.Errorf("unknown trigger type: %s", rule.Trigger) - } - - if err != nil { - return "", err - } - - // Only add stateChangesOnly to new rules - if rule.ID == "" { - trigger += ` - .stateChangesOnly() - ` - } - - trigger += AllAlerts - - if rule.Details != "" { - trigger += Details - } - return trigger, nil -} - -func relativeTrigger(rule chronograf.AlertRule) (string, error) { - op, err := kapaOperator(rule.TriggerValues.Operator) - if err != nil { - return "", err - } - if rule.TriggerValues.Change == ChangePercent { - return fmt.Sprintf(RelativePercentTrigger, op), nil - } else if rule.TriggerValues.Change == ChangeAmount { - return fmt.Sprintf(RelativeAbsoluteTrigger, op), nil - } else { - return "", fmt.Errorf("unknown change type %s", rule.TriggerValues.Change) - } -} - -func thresholdTrigger(rule chronograf.AlertRule) (string, error) { - op, err := kapaOperator(rule.TriggerValues.Operator) - if err != nil { - return "", err - } - return fmt.Sprintf(ThresholdTrigger, op), nil -} - -func thresholdRangeTrigger(rule chronograf.AlertRule) (string, error) { - ops, err := rangeOperators(rule.TriggerValues.Operator) - if err != nil { - return "", err - } - var iops = make([]interface{}, len(ops)) - for i, o := range ops { - iops[i] = o - } - return fmt.Sprintf(ThresholdRangeTrigger, iops...), nil -} diff --git a/chronograf/.kapacitor/triggers_test.go b/chronograf/.kapacitor/triggers_test.go deleted file mode 100644 index 1e09bc563aa..00000000000 --- a/chronograf/.kapacitor/triggers_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package kapacitor - -import ( - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestTrigger(t *testing.T) { - tests := []struct { - name string - rule chronograf.AlertRule - want string - wantErr bool - }{ - { - name: "Test Deadman", - rule: chronograf.AlertRule{ - Trigger: "deadman", - }, - want: `var trigger = data - |deadman(threshold, period) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) -`, - wantErr: false, - }, - { - name: "Test Relative", - rule: chronograf.AlertRule{ - Trigger: "relative", - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Change: "% change", - }, - }, - want: `var past = data - |shift(shift) - -var current = data - -var trigger = past - |join(current) - .as('past', 'current') - |eval(lambda: abs(float("current.value" - "past.value")) / float("past.value") * 100.0) - .keep() - .as('value') - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) -`, - wantErr: false, - }, - { - name: "Test Relative percent change", - rule: chronograf.AlertRule{ - Trigger: "relative", - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - Change: "change", - }, - }, - want: `var past = data - |shift(shift) - -var current = data - -var trigger = past - |join(current) - .as('past', 'current') - |eval(lambda: float("current.value" - "past.value")) - .keep() - .as('value') - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) -`, - wantErr: false, - }, - { - name: "Test Threshold", - rule: chronograf.AlertRule{ - Trigger: "threshold", - TriggerValues: chronograf.TriggerValues{ - Operator: "greater than", - }, - }, - want: `var trigger = data - |alert() - .crit(lambda: "value" > crit) - .stateChangesOnly() - .message(message) - .id(idVar) - .idTag(idTag) - .levelTag(levelTag) - .messageField(messageField) - .durationField(durationField) -`, - wantErr: false, - }, - { - name: "Test Invalid", - rule: chronograf.AlertRule{ - Trigger: "invalid", - }, - want: ``, - wantErr: true, - }, - } - for _, tt := range tests { - got, err := Trigger(tt.rule) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Trigger() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - formatted, err := formatTick(got) - if err != nil { - t.Errorf("%q. formatTick() error = %v", tt.name, err) - continue - } - if string(formatted) != tt.want { - t.Errorf("%q. Trigger() = \n%v\n want \n%v\n", tt.name, string(formatted), tt.want) - } - } -} diff --git a/chronograf/.kapacitor/validate.go b/chronograf/.kapacitor/validate.go deleted file mode 100644 index 3a1d12cec2d..00000000000 --- a/chronograf/.kapacitor/validate.go +++ /dev/null @@ -1,67 +0,0 @@ -package kapacitor - -import ( - "bytes" - "fmt" - "strings" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/kapacitor/pipeline" - "github.com/influxdata/kapacitor/tick" - "github.com/influxdata/kapacitor/tick/ast" - "github.com/influxdata/kapacitor/tick/stateful" -) - -// ValidateAlert checks if the alert is a valid kapacitor alert service. -func ValidateAlert(service string) error { - // Simple tick script to check alert service. - // If a pipeline cannot be created then we know this is an invalid - // service. At least with this version of kapacitor! - script := fmt.Sprintf("stream|from()|alert()%s", service) - return validateTick(chronograf.TICKScript(script)) -} - -func formatTick(tickscript string) (chronograf.TICKScript, error) { - node, err := ast.Parse(tickscript) - if err != nil { - return "", err - } - - output := new(bytes.Buffer) - node.Format(output, "", true) - return chronograf.TICKScript(output.String()), nil -} - -func validateTick(script chronograf.TICKScript) error { - _, err := newPipeline(script) - return err -} - -func newPipeline(script chronograf.TICKScript) (*pipeline.Pipeline, error) { - edge := pipeline.StreamEdge - if strings.Contains(string(script), "batch") { - edge = pipeline.BatchEdge - } - - scope := stateful.NewScope() - predefinedVars := map[string]tick.Var{} - return pipeline.CreatePipeline(string(script), edge, scope, &deadman{}, predefinedVars) -} - -// deadman is an empty implementation of a kapacitor DeadmanService to allow CreatePipeline -var _ pipeline.DeadmanService = &deadman{} - -type deadman struct { - interval time.Duration - threshold float64 - id string - message string - global bool -} - -func (d deadman) Interval() time.Duration { return d.interval } -func (d deadman) Threshold() float64 { return d.threshold } -func (d deadman) Id() string { return d.id } -func (d deadman) Message() string { return d.message } -func (d deadman) Global() bool { return d.global } diff --git a/chronograf/.kapacitor/validate_test.go b/chronograf/.kapacitor/validate_test.go deleted file mode 100644 index 41f997d120e..00000000000 --- a/chronograf/.kapacitor/validate_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package kapacitor - -import "testing" -import "github.com/influxdata/influxdb/v2/chronograf" - -func TestValidateAlert(t *testing.T) { - tests := []struct { - name string - service string - wantErr bool - }{ - { - name: "Test valid template alert", - service: ".slack()", - wantErr: false, - }, - { - name: "Test invalid template alert", - service: ".invalid()", - wantErr: true, - }, - } - for _, tt := range tests { - if err := ValidateAlert(tt.service); (err != nil) != tt.wantErr { - t.Errorf("%q. ValidateAlert() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - } -} - -func Test_validateTick(t *testing.T) { - tests := []struct { - name string - script chronograf.TICKScript - wantErr bool - }{ - { - name: "Valid Script", - script: "stream|from()", - wantErr: false, - }, - { - name: "Invalid Script", - script: "stream|nothing", - wantErr: true, - }, - } - for _, tt := range tests { - if err := validateTick(tt.script); (err != nil) != tt.wantErr { - t.Errorf("%q. validateTick() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - } -} diff --git a/chronograf/.kapacitor/vars.go b/chronograf/.kapacitor/vars.go deleted file mode 100644 index b0e2eeb8303..00000000000 --- a/chronograf/.kapacitor/vars.go +++ /dev/null @@ -1,271 +0,0 @@ -package kapacitor - -import ( - "fmt" - "sort" - "strconv" - "strings" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var ( - // Database is the output database for alerts. - Database = "chronograf" - // RP will be autogen for alerts because it is default. - RP = "autogen" - // Measurement will be alerts so that the app knows where to get this data. - Measurement = "alerts" - // IDTag is the output tag key for the ID of the alert - IDTag = "alertID" - //LevelTag is the output tag key for the alert level information - LevelTag = "level" - // MessageField is the output field key for the message in the alert - MessageField = "message" - // DurationField is the output field key for the duration of the alert - DurationField = "duration" -) - -// Vars builds the top level vars for a kapacitor alert script -func Vars(rule chronograf.AlertRule) (string, error) { - common, err := commonVars(rule) - if err != nil { - return "", err - } - - switch rule.Trigger { - case Threshold: - if rule.TriggerValues.RangeValue == "" { - vars := ` - %s - var crit = %s - ` - return fmt.Sprintf(vars, common, formatValue(rule.TriggerValues.Value)), nil - } - vars := ` - %s - var lower = %s - var upper = %s -` - return fmt.Sprintf(vars, - common, - rule.TriggerValues.Value, - rule.TriggerValues.RangeValue), nil - case Relative: - vars := ` - %s - var shift = %s - var crit = %s - ` - return fmt.Sprintf(vars, - common, - rule.TriggerValues.Shift, - rule.TriggerValues.Value, - ), nil - case Deadman: - vars := ` - %s - var threshold = %s - ` - return fmt.Sprintf(vars, - common, - "0.0", // deadman threshold hardcoded to zero - ), nil - default: - return "", fmt.Errorf("unknown trigger mechanism") - } -} - -// NotEmpty is an error collector checking if strings are empty values -type NotEmpty struct { - Err error -} - -// Valid checks if string s is empty and if so reports an error using name -func (n *NotEmpty) Valid(name, s string) error { - if n.Err != nil { - return n.Err - - } - if s == "" { - n.Err = fmt.Errorf("%s cannot be an empty string", name) - } - return n.Err -} - -// Escape sanitizes strings with single quotes for kapacitor -func Escape(str string) string { - return strings.Replace(str, "'", `\'`, -1) -} - -func commonVars(rule chronograf.AlertRule) (string, error) { - n := new(NotEmpty) - n.Valid("database", rule.Query.Database) - n.Valid("retention policy", rule.Query.RetentionPolicy) - n.Valid("measurement", rule.Query.Measurement) - n.Valid("alert name", rule.Name) - n.Valid("trigger type", rule.Trigger) - if n.Err != nil { - return "", n.Err - } - - wind, err := window(rule) - if err != nil { - return "", err - } - - common := ` - var db = '%s' - var rp = '%s' - var measurement = '%s' - var groupBy = %s - var whereFilter = %s - %s - - var name = '%s' - var idVar = %s - var message = '%s' - var idTag = '%s' - var levelTag = '%s' - var messageField = '%s' - var durationField = '%s' - - var outputDB = '%s' - var outputRP = '%s' - var outputMeasurement = '%s' - var triggerType = '%s' - ` - res := fmt.Sprintf(common, - Escape(rule.Query.Database), - Escape(rule.Query.RetentionPolicy), - Escape(rule.Query.Measurement), - groupBy(rule.Query), - whereFilter(rule.Query), - wind, - Escape(rule.Name), - idVar(rule.Query), - Escape(rule.Message), - IDTag, - LevelTag, - MessageField, - DurationField, - Database, - RP, - Measurement, - rule.Trigger, - ) - - if rule.Details != "" { - res += fmt.Sprintf(` - var details = '%s' - `, rule.Details) - } - return res, nil -} - -// window is only used if deadman or threshold/relative with aggregate. Will return empty -// if no period. -func window(rule chronograf.AlertRule) (string, error) { - if rule.Trigger == Deadman { - if rule.TriggerValues.Period == "" { - return "", fmt.Errorf("period cannot be an empty string in deadman alert") - } - return fmt.Sprintf("var period = %s", rule.TriggerValues.Period), nil - - } - // Period only makes sense if the field has a been grouped via a time duration. - for _, field := range rule.Query.Fields { - if field.Type == "func" { - n := new(NotEmpty) - n.Valid("group by time", rule.Query.GroupBy.Time) - n.Valid("every", rule.Every) - if n.Err != nil { - return "", n.Err - } - return fmt.Sprintf("var period = %s\nvar every = %s", rule.Query.GroupBy.Time, rule.Every), nil - } - } - return "", nil -} - -func groupBy(q *chronograf.QueryConfig) string { - groups := []string{} - if q != nil { - for _, tag := range q.GroupBy.Tags { - groups = append(groups, fmt.Sprintf("'%s'", tag)) - } - } - return "[" + strings.Join(groups, ",") + "]" -} - -func idVar(q *chronograf.QueryConfig) string { - if len(q.GroupBy.Tags) > 0 { - return `name + ':{{.Group}}'` - } - return "name" -} - -func field(q *chronograf.QueryConfig) (string, error) { - if q == nil { - return "", fmt.Errorf("no fields set in query") - } - if len(q.Fields) != 1 { - return "", fmt.Errorf("expect only one field but found %d", len(q.Fields)) - } - field := q.Fields[0] - if field.Type == "func" { - for _, arg := range field.Args { - if arg.Type == "field" { - f, ok := arg.Value.(string) - if !ok { - return "", fmt.Errorf("field value %v is should be string but is %T", arg.Value, arg.Value) - } - return f, nil - } - } - return "", fmt.Errorf("no fields set in query") - } - f, ok := field.Value.(string) - if !ok { - return "", fmt.Errorf("field value %v is should be string but is %T", field.Value, field.Value) - } - return f, nil -} - -func whereFilter(q *chronograf.QueryConfig) string { - if q != nil { - operator := "==" - if !q.AreTagsAccepted { - operator = "!=" - } - - outer := []string{} - for tag, values := range q.Tags { - inner := []string{} - for _, value := range values { - inner = append(inner, fmt.Sprintf(`"%s" %s '%s'`, tag, operator, value)) - } - outer = append(outer, "("+strings.Join(inner, " OR ")+")") - } - if len(outer) > 0 { - sort.Strings(outer) - return "lambda: " + strings.Join(outer, " AND ") - } - } - return "lambda: TRUE" -} - -// formatValue return the same string if a numeric type or if it is a string -// will return it as a kapacitor formatted single-quoted string -func formatValue(value string) string { - // Test if numeric if it can be converted to a float - if _, err := strconv.ParseFloat(value, 64); err == nil { - return value - } - - // If the value is a kapacitor boolean value perform no formatting - if value == "TRUE" || value == "FALSE" { - return value - } - return "'" + Escape(value) + "'" -} diff --git a/chronograf/.kapacitor/vars_test.go b/chronograf/.kapacitor/vars_test.go deleted file mode 100644 index 871063e1ea7..00000000000 --- a/chronograf/.kapacitor/vars_test.go +++ /dev/null @@ -1,87 +0,0 @@ -package kapacitor - -import ( - "fmt" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestVarsCritStringEqual(t *testing.T) { - alert := chronograf.AlertRule{ - Name: "name", - Trigger: "threshold", - TriggerValues: chronograf.TriggerValues{ - Operator: "equal to", - Value: "DOWN", - }, - Every: "30s", - Query: &chronograf.QueryConfig{ - Database: "telegraf", - Measurement: "haproxy", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "status", - Type: "field", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "10m", - Tags: []string{"pxname"}, - }, - AreTagsAccepted: true, - }, - } - - raw, err := Vars(alert) - if err != nil { - fmt.Printf("%s", raw) - t.Fatalf("Error generating alert: %v %s", err, raw) - } - - tick, err := formatTick(raw) - if err != nil { - t.Errorf("Error formatting alert: %v %s", err, raw) - } - - if err := validateTick(tick); err != nil { - t.Errorf("Error validating alert: %v %s", err, tick) - } -} - -func Test_formatValue(t *testing.T) { - tests := []struct { - name string - value string - want string - }{ - { - name: "parses floats", - value: "3.14", - want: "3.14", - }, - { - name: "parses booleans", - value: "TRUE", - want: "TRUE", - }, - { - name: "single quotes for strings", - value: "up", - want: "'up'", - }, - { - name: "handles escaping of single quotes", - value: "down's", - want: "'down\\'s'", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := formatValue(tt.value); got != tt.want { - t.Errorf("formatValue() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/chronograf/bolt/base.go b/chronograf/bolt/base.go deleted file mode 100644 index 4719d8cb5d4..00000000000 --- a/chronograf/bolt/base.go +++ /dev/null @@ -1,94 +0,0 @@ -package bolt - -import ( - "time" - - bolt "go.etcd.io/bbolt" -) - -// SchemaVersionBucket stores ids of completed migrations -var SchemaVersionBucket = []byte("SchemaVersions") - -// IsMigrationComplete checks for the presence of a particular migration id -func IsMigrationComplete(db *bolt.DB, id string) (bool, error) { - complete := false - if err := db.View(func(tx *bolt.Tx) error { - migration := tx.Bucket(SchemaVersionBucket).Get([]byte(id)) - if migration != nil { - complete = true - } - return nil - }); err != nil { - return true, err - } - - return complete, nil -} - -// MarkMigrationAsComplete adds the migration id to the schema bucket -func MarkMigrationAsComplete(db *bolt.DB, id string) error { - if err := db.Update(func(tx *bolt.Tx) error { - now := time.Now().UTC().Format(time.RFC3339) - return tx.Bucket(SchemaVersionBucket).Put([]byte(id), []byte(now)) - }); err != nil { - return err - } - - return nil -} - -// Migration defines a database state/schema transition -// ID: After the migration is run, this id is stored in the database. -// We don't want to run a state transition twice -// Up: The forward-transition function. After a version upgrade, a number -// of these will run on database startup in order to bring a user's -// schema in line with struct definitions in the new version. -// Down: The backward-transition function. We don't expect these to be -// run on a user's database -- if the user needs to rollback -// to a previous version, it will be easier for them to replace -// their current database with one of their backups. The primary -// purpose of a Down() function is to help contributors move across -// development branches that have different schema definitions. -type Migration struct { - ID string - Up func(db *bolt.DB) error - Down func(db *bolt.DB) error -} - -// Migrate runs one migration's Up() function, if it has not already been run -func (m Migration) Migrate(client *Client) error { - complete, err := IsMigrationComplete(client.db, m.ID) - if err != nil { - return err - } - if complete { - return nil - } - - if client.logger != nil { - client.logger.Info("Running migration ", m.ID, "") - } - - if err = m.Up(client.db); err != nil { - return err - } - - return MarkMigrationAsComplete(client.db, m.ID) -} - -// MigrateAll iterates through all known migrations and runs them in order -func MigrateAll(client *Client) error { - for _, m := range migrations { - err := m.Migrate(client) - - if err != nil { - return err - } - } - - return nil -} - -var migrations = []Migration{ - changeIntervalToDuration, -} diff --git a/chronograf/bolt/bolt_test.go b/chronograf/bolt/bolt_test.go deleted file mode 100644 index 7f452e59338..00000000000 --- a/chronograf/bolt/bolt_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package bolt_test - -import ( - "context" - "errors" - "io/ioutil" - "os" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -// TestNow is a set time for testing. -var TestNow = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) - -// TestClient wraps *bolt.Client. -type TestClient struct { - *bolt.Client -} - -// NewTestClient creates new *bolt.Client with a set time and temp path. -func NewTestClient() (*TestClient, error) { - f, err := ioutil.TempFile("", "chronograf-bolt-") - if err != nil { - return nil, errors.New("unable to open temporary boltdb file") - } - f.Close() - - c := &TestClient{ - Client: bolt.NewClient(), - } - c.Path = f.Name() - c.Now = func() time.Time { return TestNow } - - build := chronograf.BuildInfo{ - Version: "version", - Commit: "commit", - } - - c.Open(context.TODO(), mocks.NewLogger(), build) - - return c, nil -} - -func (c *TestClient) Close() error { - defer os.Remove(c.Path) - return c.Client.Close() -} diff --git a/chronograf/bolt/build.go b/chronograf/bolt/build.go deleted file mode 100644 index 3386aff1017..00000000000 --- a/chronograf/bolt/build.go +++ /dev/null @@ -1,83 +0,0 @@ -package bolt - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - bolt "go.etcd.io/bbolt" -) - -// Ensure BuildStore struct implements chronograf.BuildStore interface -var _ chronograf.BuildStore = &BuildStore{} - -// BuildBucket is the bolt bucket used to store Chronograf build information -var BuildBucket = []byte("Build") - -// BuildKey is the constant key used in the bolt bucket -var BuildKey = []byte("build") - -// BuildStore is a bolt implementation to store Chronograf build information -type BuildStore struct { - client *Client -} - -// Get retrieves Chronograf build information from the database -func (s *BuildStore) Get(ctx context.Context) (chronograf.BuildInfo, error) { - var build chronograf.BuildInfo - if err := s.client.db.View(func(tx *bolt.Tx) error { - var err error - build, err = s.get(ctx, tx) - if err != nil { - return err - } - return nil - }); err != nil { - return chronograf.BuildInfo{}, err - } - - return build, nil -} - -// Update overwrites the current Chronograf build information in the database -func (s *BuildStore) Update(ctx context.Context, build chronograf.BuildInfo) error { - if err := s.client.db.Update(func(tx *bolt.Tx) error { - return s.update(ctx, build, tx) - }); err != nil { - return err - } - - return nil -} - -// Migrate simply stores the current version in the database -func (s *BuildStore) Migrate(ctx context.Context, build chronograf.BuildInfo) error { - return s.Update(ctx, build) -} - -// get retrieves the current build, falling back to a default when missing -func (s *BuildStore) get(ctx context.Context, tx *bolt.Tx) (chronograf.BuildInfo, error) { - var build chronograf.BuildInfo - defaultBuild := chronograf.BuildInfo{ - Version: "pre-1.4.0.0", - Commit: "", - } - - if bucket := tx.Bucket(BuildBucket); bucket == nil { - return defaultBuild, nil - } else if v := bucket.Get(BuildKey); v == nil { - return defaultBuild, nil - } else if err := internal.UnmarshalBuild(v, &build); err != nil { - return build, err - } - return build, nil -} - -func (s *BuildStore) update(ctx context.Context, build chronograf.BuildInfo, tx *bolt.Tx) error { - if v, err := internal.MarshalBuild(build); err != nil { - return err - } else if err := tx.Bucket(BuildBucket).Put(BuildKey, v); err != nil { - return err - } - return nil -} diff --git a/chronograf/bolt/build_test.go b/chronograf/bolt/build_test.go deleted file mode 100644 index 7a29be442ed..00000000000 --- a/chronograf/bolt/build_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package bolt_test - -// import ( -// "testing" - -// "github.com/google/go-cmp/cmp" -// "github.com/influxdata/influxdb/chronograf" -// ) - -// func -// func TestBuildStore_Get(t *testing.T) { -// type wants struct { -// build *chronograf.BuildInfo -// err error -// } -// tests := []struct { -// name string -// wants wants -// }{ -// { -// name: "When the build info is missing", -// wants: wants{ -// build: &chronograf.BuildInfo{ -// Version: "pre-1.4.0.0", -// Commit: "", -// }, -// }, -// }, -// } -// for _, tt := range tests { -// client, err := NewTestClient() -// if err != nil { -// t.Fatal(err) -// } -// if err := client.Open(context.TODO()); err != nil { -// t.Fatal(err) -// } -// defer client.Close() - -// b := client.BuildStore -// got, err := b.Get(context.Background()) -// if (tt.wants.err != nil) != (err != nil) { -// t.Errorf("%q. BuildStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) -// continue -// } -// if diff := cmp.Diff(got, tt.wants.build); diff != "" { -// t.Errorf("%q. BuildStore.Get():\n-got/+want\ndiff %s", tt.name, diff) -// } -// } -// } - -// func TestBuildStore_Update(t *testing.T) { - -// } diff --git a/chronograf/bolt/change_interval_to_duration.go b/chronograf/bolt/change_interval_to_duration.go deleted file mode 100644 index ba4aa659f8a..00000000000 --- a/chronograf/bolt/change_interval_to_duration.go +++ /dev/null @@ -1,1424 +0,0 @@ -package bolt - -import ( - "log" - "strings" - - "github.com/gogo/protobuf/proto" - bolt "go.etcd.io/bbolt" -) - -// changeIntervalToDuration -// Before, we supported queries that included `GROUP BY :interval:` -// After, we only support queries with `GROUP BY time(:interval:)` -// thereby allowing non_negative_derivative(_____, :interval) -var changeIntervalToDuration = Migration{ - ID: "59b0cda4fc7909ff84ee5c4f9cb4b655b6a26620", - Up: up, - Down: down, -} - -func updateDashboard(board *Dashboard) { - for _, cell := range board.Cells { - for _, query := range cell.Queries { - query.Command = strings.Replace(query.Command, ":interval:", "time(:interval:)", -1) - } - } -} - -var up = func(db *bolt.DB) error { - // For each dashboard - err := db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket(dashboardBucket) - err := bucket.ForEach(func(id, data []byte) error { - board := &Dashboard{} - - err := proto.Unmarshal(data, board) - if err != nil { - log.Fatal("unmarshalling error: ", err) - } - - // Migrate the dashboard - updateDashboard(board) - - data, err = proto.Marshal(board) - if err != nil { - log.Fatal("marshaling error: ", err) - } - - err = bucket.Put(id, data) - if err != nil { - log.Fatal("error updating dashboard: ", err) - } - - return nil - }) - - if err != nil { - log.Fatal("error updating dashboards: ", err) - } - - return nil - }) - - if err != nil { - return err - } - - return nil -} - -var down = func(db *bolt.DB) error { - return nil -} - -/* - Import protobuf types and bucket names that are pertinent to this migration. - This isolates the migration from the codebase, and prevents a future change - to a type definition from invalidating the migration functions. -*/ -var dashboardBucket = []byte("Dashoard") // N.B. leave the misspelling for backwards-compat! - -type Source struct { - ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Type string `protobuf:"bytes,3,opt,name=Type,proto3" json:"Type,omitempty"` - Username string `protobuf:"bytes,4,opt,name=Username,proto3" json:"Username,omitempty"` - Password string `protobuf:"bytes,5,opt,name=Password,proto3" json:"Password,omitempty"` - URL string `protobuf:"bytes,6,opt,name=URL,proto3" json:"URL,omitempty"` - Default bool `protobuf:"varint,7,opt,name=Default,proto3" json:"Default,omitempty"` - Telegraf string `protobuf:"bytes,8,opt,name=Telegraf,proto3" json:"Telegraf,omitempty"` - InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"` - MetaURL string `protobuf:"bytes,10,opt,name=MetaURL,proto3" json:"MetaURL,omitempty"` - SharedSecret string `protobuf:"bytes,11,opt,name=SharedSecret,proto3" json:"SharedSecret,omitempty"` - Organization string `protobuf:"bytes,12,opt,name=Organization,proto3" json:"Organization,omitempty"` - Role string `protobuf:"bytes,13,opt,name=Role,proto3" json:"Role,omitempty"` -} - -func (m *Source) Reset() { *m = Source{} } -func (m *Source) String() string { return proto.CompactTextString(m) } -func (*Source) ProtoMessage() {} -func (*Source) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} } - -func (m *Source) GetID() int64 { - if m != nil { - return m.ID - } - return 0 -} - -func (m *Source) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Source) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Source) GetUsername() string { - if m != nil { - return m.Username - } - return "" -} - -func (m *Source) GetPassword() string { - if m != nil { - return m.Password - } - return "" -} - -func (m *Source) GetURL() string { - if m != nil { - return m.URL - } - return "" -} - -func (m *Source) GetDefault() bool { - if m != nil { - return m.Default - } - return false -} - -func (m *Source) GetTelegraf() string { - if m != nil { - return m.Telegraf - } - return "" -} - -func (m *Source) GetInsecureSkipVerify() bool { - if m != nil { - return m.InsecureSkipVerify - } - return false -} - -func (m *Source) GetMetaURL() string { - if m != nil { - return m.MetaURL - } - return "" -} - -func (m *Source) GetSharedSecret() string { - if m != nil { - return m.SharedSecret - } - return "" -} - -func (m *Source) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -func (m *Source) GetRole() string { - if m != nil { - return m.Role - } - return "" -} - -type Dashboard struct { - ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Cells []*DashboardCell `protobuf:"bytes,3,rep,name=cells" json:"cells,omitempty"` - Templates []*Template `protobuf:"bytes,4,rep,name=templates" json:"templates,omitempty"` - Organization string `protobuf:"bytes,5,opt,name=Organization,proto3" json:"Organization,omitempty"` -} - -func (m *Dashboard) Reset() { *m = Dashboard{} } -func (m *Dashboard) String() string { return proto.CompactTextString(m) } -func (*Dashboard) ProtoMessage() {} -func (*Dashboard) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} } - -func (m *Dashboard) GetID() int64 { - if m != nil { - return m.ID - } - return 0 -} - -func (m *Dashboard) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Dashboard) GetCells() []*DashboardCell { - if m != nil { - return m.Cells - } - return nil -} - -func (m *Dashboard) GetTemplates() []*Template { - if m != nil { - return m.Templates - } - return nil -} - -func (m *Dashboard) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -type DashboardCell struct { - X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` - Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` - W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"` - H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"` - Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"` - Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` - Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` - ID string `protobuf:"bytes,8,opt,name=ID,proto3" json:"ID,omitempty"` - Axes map[string]*Axis `protobuf:"bytes,9,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"` - Colors []*Color `protobuf:"bytes,10,rep,name=colors" json:"colors,omitempty"` - Legend *Legend `protobuf:"bytes,11,opt,name=legend" json:"legend,omitempty"` - TableOptions *TableOptions `protobuf:"bytes,12,opt,name=tableOptions" json:"tableOptions,omitempty"` -} - -func (m *DashboardCell) Reset() { *m = DashboardCell{} } -func (m *DashboardCell) String() string { return proto.CompactTextString(m) } -func (*DashboardCell) ProtoMessage() {} -func (*DashboardCell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{2} } - -func (m *DashboardCell) GetX() int32 { - if m != nil { - return m.X - } - return 0 -} - -func (m *DashboardCell) GetY() int32 { - if m != nil { - return m.Y - } - return 0 -} - -func (m *DashboardCell) GetW() int32 { - if m != nil { - return m.W - } - return 0 -} - -func (m *DashboardCell) GetH() int32 { - if m != nil { - return m.H - } - return 0 -} - -func (m *DashboardCell) GetQueries() []*Query { - if m != nil { - return m.Queries - } - return nil -} - -func (m *DashboardCell) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *DashboardCell) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *DashboardCell) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *DashboardCell) GetAxes() map[string]*Axis { - if m != nil { - return m.Axes - } - return nil -} - -func (m *DashboardCell) GetColors() []*Color { - if m != nil { - return m.Colors - } - return nil -} - -func (m *DashboardCell) GetLegend() *Legend { - if m != nil { - return m.Legend - } - return nil -} - -func (m *DashboardCell) GetTableOptions() *TableOptions { - if m != nil { - return m.TableOptions - } - return nil -} - -type TableOptions struct { - VerticalTimeAxis bool `protobuf:"varint,2,opt,name=verticalTimeAxis,proto3" json:"verticalTimeAxis,omitempty"` - SortBy *RenamableField `protobuf:"bytes,3,opt,name=sortBy" json:"sortBy,omitempty"` - Wrapping string `protobuf:"bytes,4,opt,name=wrapping,proto3" json:"wrapping,omitempty"` - FieldNames []*RenamableField `protobuf:"bytes,5,rep,name=fieldNames" json:"fieldNames,omitempty"` - FixFirstColumn bool `protobuf:"varint,6,opt,name=fixFirstColumn,proto3" json:"fixFirstColumn,omitempty"` -} - -func (m *TableOptions) Reset() { *m = TableOptions{} } -func (m *TableOptions) String() string { return proto.CompactTextString(m) } -func (*TableOptions) ProtoMessage() {} -func (*TableOptions) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{3} } - -func (m *TableOptions) GetVerticalTimeAxis() bool { - if m != nil { - return m.VerticalTimeAxis - } - return false -} - -func (m *TableOptions) GetSortBy() *RenamableField { - if m != nil { - return m.SortBy - } - return nil -} - -func (m *TableOptions) GetWrapping() string { - if m != nil { - return m.Wrapping - } - return "" -} - -func (m *TableOptions) GetFieldNames() []*RenamableField { - if m != nil { - return m.FieldNames - } - return nil -} - -func (m *TableOptions) GetFixFirstColumn() bool { - if m != nil { - return m.FixFirstColumn - } - return false -} - -type RenamableField struct { - InternalName string `protobuf:"bytes,1,opt,name=internalName,proto3" json:"internalName,omitempty"` - DisplayName string `protobuf:"bytes,2,opt,name=displayName,proto3" json:"displayName,omitempty"` - Visible bool `protobuf:"varint,3,opt,name=visible,proto3" json:"visible,omitempty"` -} - -func (m *RenamableField) Reset() { *m = RenamableField{} } -func (m *RenamableField) String() string { return proto.CompactTextString(m) } -func (*RenamableField) ProtoMessage() {} -func (*RenamableField) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{4} } - -func (m *RenamableField) GetInternalName() string { - if m != nil { - return m.InternalName - } - return "" -} - -func (m *RenamableField) GetDisplayName() string { - if m != nil { - return m.DisplayName - } - return "" -} - -func (m *RenamableField) GetVisible() bool { - if m != nil { - return m.Visible - } - return false -} - -type Color struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"` - Hex string `protobuf:"bytes,3,opt,name=Hex,proto3" json:"Hex,omitempty"` - Name string `protobuf:"bytes,4,opt,name=Name,proto3" json:"Name,omitempty"` - Value string `protobuf:"bytes,5,opt,name=Value,proto3" json:"Value,omitempty"` -} - -func (m *Color) Reset() { *m = Color{} } -func (m *Color) String() string { return proto.CompactTextString(m) } -func (*Color) ProtoMessage() {} -func (*Color) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{5} } - -func (m *Color) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Color) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Color) GetHex() string { - if m != nil { - return m.Hex - } - return "" -} - -func (m *Color) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Color) GetValue() string { - if m != nil { - return m.Value - } - return "" -} - -type Legend struct { - Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"` - Orientation string `protobuf:"bytes,2,opt,name=Orientation,proto3" json:"Orientation,omitempty"` -} - -func (m *Legend) Reset() { *m = Legend{} } -func (m *Legend) String() string { return proto.CompactTextString(m) } -func (*Legend) ProtoMessage() {} -func (*Legend) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{6} } - -func (m *Legend) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Legend) GetOrientation() string { - if m != nil { - return m.Orientation - } - return "" -} - -type Axis struct { - LegacyBounds []int64 `protobuf:"varint,1,rep,packed,name=legacyBounds" json:"legacyBounds,omitempty"` - Bounds []string `protobuf:"bytes,2,rep,name=bounds" json:"bounds,omitempty"` - Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"` - Prefix string `protobuf:"bytes,4,opt,name=prefix,proto3" json:"prefix,omitempty"` - Suffix string `protobuf:"bytes,5,opt,name=suffix,proto3" json:"suffix,omitempty"` - Base string `protobuf:"bytes,6,opt,name=base,proto3" json:"base,omitempty"` - Scale string `protobuf:"bytes,7,opt,name=scale,proto3" json:"scale,omitempty"` -} - -func (m *Axis) Reset() { *m = Axis{} } -func (m *Axis) String() string { return proto.CompactTextString(m) } -func (*Axis) ProtoMessage() {} -func (*Axis) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{7} } - -func (m *Axis) GetLegacyBounds() []int64 { - if m != nil { - return m.LegacyBounds - } - return nil -} - -func (m *Axis) GetBounds() []string { - if m != nil { - return m.Bounds - } - return nil -} - -func (m *Axis) GetLabel() string { - if m != nil { - return m.Label - } - return "" -} - -func (m *Axis) GetPrefix() string { - if m != nil { - return m.Prefix - } - return "" -} - -func (m *Axis) GetSuffix() string { - if m != nil { - return m.Suffix - } - return "" -} - -func (m *Axis) GetBase() string { - if m != nil { - return m.Base - } - return "" -} - -func (m *Axis) GetScale() string { - if m != nil { - return m.Scale - } - return "" -} - -type Template struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - TempVar string `protobuf:"bytes,2,opt,name=temp_var,json=tempVar,proto3" json:"temp_var,omitempty"` - Values []*TemplateValue `protobuf:"bytes,3,rep,name=values" json:"values,omitempty"` - Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"` - Label string `protobuf:"bytes,5,opt,name=label,proto3" json:"label,omitempty"` - Query *TemplateQuery `protobuf:"bytes,6,opt,name=query" json:"query,omitempty"` -} - -func (m *Template) Reset() { *m = Template{} } -func (m *Template) String() string { return proto.CompactTextString(m) } -func (*Template) ProtoMessage() {} -func (*Template) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{8} } - -func (m *Template) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Template) GetTempVar() string { - if m != nil { - return m.TempVar - } - return "" -} - -func (m *Template) GetValues() []*TemplateValue { - if m != nil { - return m.Values - } - return nil -} - -func (m *Template) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Template) GetLabel() string { - if m != nil { - return m.Label - } - return "" -} - -func (m *Template) GetQuery() *TemplateQuery { - if m != nil { - return m.Query - } - return nil -} - -type TemplateValue struct { - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` - Selected bool `protobuf:"varint,3,opt,name=selected,proto3" json:"selected,omitempty"` -} - -func (m *TemplateValue) Reset() { *m = TemplateValue{} } -func (m *TemplateValue) String() string { return proto.CompactTextString(m) } -func (*TemplateValue) ProtoMessage() {} -func (*TemplateValue) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{9} } - -func (m *TemplateValue) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *TemplateValue) GetValue() string { - if m != nil { - return m.Value - } - return "" -} - -func (m *TemplateValue) GetSelected() bool { - if m != nil { - return m.Selected - } - return false -} - -type TemplateQuery struct { - Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` - Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,omitempty"` - Rp string `protobuf:"bytes,3,opt,name=rp,proto3" json:"rp,omitempty"` - Measurement string `protobuf:"bytes,4,opt,name=measurement,proto3" json:"measurement,omitempty"` - TagKey string `protobuf:"bytes,5,opt,name=tag_key,json=tagKey,proto3" json:"tag_key,omitempty"` - FieldKey string `protobuf:"bytes,6,opt,name=field_key,json=fieldKey,proto3" json:"field_key,omitempty"` -} - -func (m *TemplateQuery) Reset() { *m = TemplateQuery{} } -func (m *TemplateQuery) String() string { return proto.CompactTextString(m) } -func (*TemplateQuery) ProtoMessage() {} -func (*TemplateQuery) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{10} } - -func (m *TemplateQuery) GetCommand() string { - if m != nil { - return m.Command - } - return "" -} - -func (m *TemplateQuery) GetDb() string { - if m != nil { - return m.Db - } - return "" -} - -func (m *TemplateQuery) GetRp() string { - if m != nil { - return m.Rp - } - return "" -} - -func (m *TemplateQuery) GetMeasurement() string { - if m != nil { - return m.Measurement - } - return "" -} - -func (m *TemplateQuery) GetTagKey() string { - if m != nil { - return m.TagKey - } - return "" -} - -func (m *TemplateQuery) GetFieldKey() string { - if m != nil { - return m.FieldKey - } - return "" -} - -type Server struct { - ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Username string `protobuf:"bytes,3,opt,name=Username,proto3" json:"Username,omitempty"` - Password string `protobuf:"bytes,4,opt,name=Password,proto3" json:"Password,omitempty"` - URL string `protobuf:"bytes,5,opt,name=URL,proto3" json:"URL,omitempty"` - SrcID int64 `protobuf:"varint,6,opt,name=SrcID,proto3" json:"SrcID,omitempty"` - Active bool `protobuf:"varint,7,opt,name=Active,proto3" json:"Active,omitempty"` - Organization string `protobuf:"bytes,8,opt,name=Organization,proto3" json:"Organization,omitempty"` - InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"` -} - -func (m *Server) Reset() { *m = Server{} } -func (m *Server) String() string { return proto.CompactTextString(m) } -func (*Server) ProtoMessage() {} -func (*Server) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{11} } - -func (m *Server) GetID() int64 { - if m != nil { - return m.ID - } - return 0 -} - -func (m *Server) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Server) GetUsername() string { - if m != nil { - return m.Username - } - return "" -} - -func (m *Server) GetPassword() string { - if m != nil { - return m.Password - } - return "" -} - -func (m *Server) GetURL() string { - if m != nil { - return m.URL - } - return "" -} - -func (m *Server) GetSrcID() int64 { - if m != nil { - return m.SrcID - } - return 0 -} - -func (m *Server) GetActive() bool { - if m != nil { - return m.Active - } - return false -} - -func (m *Server) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -func (m *Server) GetInsecureSkipVerify() bool { - if m != nil { - return m.InsecureSkipVerify - } - return false -} - -type Layout struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"` - Measurement string `protobuf:"bytes,3,opt,name=Measurement,proto3" json:"Measurement,omitempty"` - Cells []*Cell `protobuf:"bytes,4,rep,name=Cells" json:"Cells,omitempty"` - Autoflow bool `protobuf:"varint,5,opt,name=Autoflow,proto3" json:"Autoflow,omitempty"` -} - -func (m *Layout) Reset() { *m = Layout{} } -func (m *Layout) String() string { return proto.CompactTextString(m) } -func (*Layout) ProtoMessage() {} -func (*Layout) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{12} } - -func (m *Layout) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Layout) GetApplication() string { - if m != nil { - return m.Application - } - return "" -} - -func (m *Layout) GetMeasurement() string { - if m != nil { - return m.Measurement - } - return "" -} - -func (m *Layout) GetCells() []*Cell { - if m != nil { - return m.Cells - } - return nil -} - -func (m *Layout) GetAutoflow() bool { - if m != nil { - return m.Autoflow - } - return false -} - -type Cell struct { - X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` - Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` - W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"` - H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"` - Queries []*Query `protobuf:"bytes,5,rep,name=queries" json:"queries,omitempty"` - I string `protobuf:"bytes,6,opt,name=i,proto3" json:"i,omitempty"` - Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"` - Yranges []int64 `protobuf:"varint,8,rep,packed,name=yranges" json:"yranges,omitempty"` - Ylabels []string `protobuf:"bytes,9,rep,name=ylabels" json:"ylabels,omitempty"` - Type string `protobuf:"bytes,10,opt,name=type,proto3" json:"type,omitempty"` - Axes map[string]*Axis `protobuf:"bytes,11,rep,name=axes" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value"` -} - -func (m *Cell) Reset() { *m = Cell{} } -func (m *Cell) String() string { return proto.CompactTextString(m) } -func (*Cell) ProtoMessage() {} -func (*Cell) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{13} } - -func (m *Cell) GetX() int32 { - if m != nil { - return m.X - } - return 0 -} - -func (m *Cell) GetY() int32 { - if m != nil { - return m.Y - } - return 0 -} - -func (m *Cell) GetW() int32 { - if m != nil { - return m.W - } - return 0 -} - -func (m *Cell) GetH() int32 { - if m != nil { - return m.H - } - return 0 -} - -func (m *Cell) GetQueries() []*Query { - if m != nil { - return m.Queries - } - return nil -} - -func (m *Cell) GetI() string { - if m != nil { - return m.I - } - return "" -} - -func (m *Cell) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Cell) GetYranges() []int64 { - if m != nil { - return m.Yranges - } - return nil -} - -func (m *Cell) GetYlabels() []string { - if m != nil { - return m.Ylabels - } - return nil -} - -func (m *Cell) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Cell) GetAxes() map[string]*Axis { - if m != nil { - return m.Axes - } - return nil -} - -type Query struct { - Command string `protobuf:"bytes,1,opt,name=Command,proto3" json:"Command,omitempty"` - DB string `protobuf:"bytes,2,opt,name=DB,proto3" json:"DB,omitempty"` - RP string `protobuf:"bytes,3,opt,name=RP,proto3" json:"RP,omitempty"` - GroupBys []string `protobuf:"bytes,4,rep,name=GroupBys" json:"GroupBys,omitempty"` - Wheres []string `protobuf:"bytes,5,rep,name=Wheres" json:"Wheres,omitempty"` - Label string `protobuf:"bytes,6,opt,name=Label,proto3" json:"Label,omitempty"` - Range *Range `protobuf:"bytes,7,opt,name=Range" json:"Range,omitempty"` - Source string `protobuf:"bytes,8,opt,name=Source,proto3" json:"Source,omitempty"` - Shifts []*TimeShift `protobuf:"bytes,9,rep,name=Shifts" json:"Shifts,omitempty"` -} - -func (m *Query) Reset() { *m = Query{} } -func (m *Query) String() string { return proto.CompactTextString(m) } -func (*Query) ProtoMessage() {} -func (*Query) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{14} } - -func (m *Query) GetCommand() string { - if m != nil { - return m.Command - } - return "" -} - -func (m *Query) GetDB() string { - if m != nil { - return m.DB - } - return "" -} - -func (m *Query) GetRP() string { - if m != nil { - return m.RP - } - return "" -} - -func (m *Query) GetGroupBys() []string { - if m != nil { - return m.GroupBys - } - return nil -} - -func (m *Query) GetWheres() []string { - if m != nil { - return m.Wheres - } - return nil -} - -func (m *Query) GetLabel() string { - if m != nil { - return m.Label - } - return "" -} - -func (m *Query) GetRange() *Range { - if m != nil { - return m.Range - } - return nil -} - -func (m *Query) GetSource() string { - if m != nil { - return m.Source - } - return "" -} - -func (m *Query) GetShifts() []*TimeShift { - if m != nil { - return m.Shifts - } - return nil -} - -type TimeShift struct { - Label string `protobuf:"bytes,1,opt,name=Label,proto3" json:"Label,omitempty"` - Unit string `protobuf:"bytes,2,opt,name=Unit,proto3" json:"Unit,omitempty"` - Quantity string `protobuf:"bytes,3,opt,name=Quantity,proto3" json:"Quantity,omitempty"` -} - -func (m *TimeShift) Reset() { *m = TimeShift{} } -func (m *TimeShift) String() string { return proto.CompactTextString(m) } -func (*TimeShift) ProtoMessage() {} -func (*TimeShift) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{15} } - -func (m *TimeShift) GetLabel() string { - if m != nil { - return m.Label - } - return "" -} - -func (m *TimeShift) GetUnit() string { - if m != nil { - return m.Unit - } - return "" -} - -func (m *TimeShift) GetQuantity() string { - if m != nil { - return m.Quantity - } - return "" -} - -type Range struct { - Upper int64 `protobuf:"varint,1,opt,name=Upper,proto3" json:"Upper,omitempty"` - Lower int64 `protobuf:"varint,2,opt,name=Lower,proto3" json:"Lower,omitempty"` -} - -func (m *Range) Reset() { *m = Range{} } -func (m *Range) String() string { return proto.CompactTextString(m) } -func (*Range) ProtoMessage() {} -func (*Range) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{16} } - -func (m *Range) GetUpper() int64 { - if m != nil { - return m.Upper - } - return 0 -} - -func (m *Range) GetLower() int64 { - if m != nil { - return m.Lower - } - return 0 -} - -type AlertRule struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - JSON string `protobuf:"bytes,2,opt,name=JSON,proto3" json:"JSON,omitempty"` - SrcID int64 `protobuf:"varint,3,opt,name=SrcID,proto3" json:"SrcID,omitempty"` - KapaID int64 `protobuf:"varint,4,opt,name=KapaID,proto3" json:"KapaID,omitempty"` -} - -func (m *AlertRule) Reset() { *m = AlertRule{} } -func (m *AlertRule) String() string { return proto.CompactTextString(m) } -func (*AlertRule) ProtoMessage() {} -func (*AlertRule) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{17} } - -func (m *AlertRule) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *AlertRule) GetJSON() string { - if m != nil { - return m.JSON - } - return "" -} - -func (m *AlertRule) GetSrcID() int64 { - if m != nil { - return m.SrcID - } - return 0 -} - -func (m *AlertRule) GetKapaID() int64 { - if m != nil { - return m.KapaID - } - return 0 -} - -type User struct { - ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Provider string `protobuf:"bytes,3,opt,name=Provider,proto3" json:"Provider,omitempty"` - Scheme string `protobuf:"bytes,4,opt,name=Scheme,proto3" json:"Scheme,omitempty"` - Roles []*Role `protobuf:"bytes,5,rep,name=Roles" json:"Roles,omitempty"` - SuperAdmin bool `protobuf:"varint,6,opt,name=SuperAdmin,proto3" json:"SuperAdmin,omitempty"` -} - -func (m *User) Reset() { *m = User{} } -func (m *User) String() string { return proto.CompactTextString(m) } -func (*User) ProtoMessage() {} -func (*User) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{18} } - -func (m *User) GetID() uint64 { - if m != nil { - return m.ID - } - return 0 -} - -func (m *User) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *User) GetProvider() string { - if m != nil { - return m.Provider - } - return "" -} - -func (m *User) GetScheme() string { - if m != nil { - return m.Scheme - } - return "" -} - -func (m *User) GetRoles() []*Role { - if m != nil { - return m.Roles - } - return nil -} - -func (m *User) GetSuperAdmin() bool { - if m != nil { - return m.SuperAdmin - } - return false -} - -type Role struct { - Organization string `protobuf:"bytes,1,opt,name=Organization,proto3" json:"Organization,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` -} - -func (m *Role) Reset() { *m = Role{} } -func (m *Role) String() string { return proto.CompactTextString(m) } -func (*Role) ProtoMessage() {} -func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{19} } - -func (m *Role) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -func (m *Role) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -type Mapping struct { - Provider string `protobuf:"bytes,1,opt,name=Provider,proto3" json:"Provider,omitempty"` - Scheme string `protobuf:"bytes,2,opt,name=Scheme,proto3" json:"Scheme,omitempty"` - ProviderOrganization string `protobuf:"bytes,3,opt,name=ProviderOrganization,proto3" json:"ProviderOrganization,omitempty"` - ID string `protobuf:"bytes,4,opt,name=ID,proto3" json:"ID,omitempty"` - Organization string `protobuf:"bytes,5,opt,name=Organization,proto3" json:"Organization,omitempty"` -} - -func (m *Mapping) Reset() { *m = Mapping{} } -func (m *Mapping) String() string { return proto.CompactTextString(m) } -func (*Mapping) ProtoMessage() {} -func (*Mapping) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{20} } - -func (m *Mapping) GetProvider() string { - if m != nil { - return m.Provider - } - return "" -} - -func (m *Mapping) GetScheme() string { - if m != nil { - return m.Scheme - } - return "" -} - -func (m *Mapping) GetProviderOrganization() string { - if m != nil { - return m.ProviderOrganization - } - return "" -} - -func (m *Mapping) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Mapping) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -type Organization struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - DefaultRole string `protobuf:"bytes,3,opt,name=DefaultRole,proto3" json:"DefaultRole,omitempty"` -} - -func (m *Organization) Reset() { *m = Organization{} } -func (m *Organization) String() string { return proto.CompactTextString(m) } -func (*Organization) ProtoMessage() {} -func (*Organization) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{21} } - -func (m *Organization) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Organization) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Organization) GetDefaultRole() string { - if m != nil { - return m.DefaultRole - } - return "" -} - -type Config struct { - Auth *AuthConfig `protobuf:"bytes,1,opt,name=Auth" json:"Auth,omitempty"` -} - -func (m *Config) Reset() { *m = Config{} } -func (m *Config) String() string { return proto.CompactTextString(m) } -func (*Config) ProtoMessage() {} -func (*Config) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{22} } - -func (m *Config) GetAuth() *AuthConfig { - if m != nil { - return m.Auth - } - return nil -} - -type AuthConfig struct { - SuperAdminNewUsers bool `protobuf:"varint,1,opt,name=SuperAdminNewUsers,proto3" json:"SuperAdminNewUsers,omitempty"` -} - -func (m *AuthConfig) Reset() { *m = AuthConfig{} } -func (m *AuthConfig) String() string { return proto.CompactTextString(m) } -func (*AuthConfig) ProtoMessage() {} -func (*AuthConfig) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{23} } - -func (m *AuthConfig) GetSuperAdminNewUsers() bool { - if m != nil { - return m.SuperAdminNewUsers - } - return false -} - -type BuildInfo struct { - Version string `protobuf:"bytes,1,opt,name=Version,proto3" json:"Version,omitempty"` - Commit string `protobuf:"bytes,2,opt,name=Commit,proto3" json:"Commit,omitempty"` -} - -func (m *BuildInfo) Reset() { *m = BuildInfo{} } -func (m *BuildInfo) String() string { return proto.CompactTextString(m) } -func (*BuildInfo) ProtoMessage() {} -func (*BuildInfo) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{24} } - -func (m *BuildInfo) GetVersion() string { - if m != nil { - return m.Version - } - return "" -} - -func (m *BuildInfo) GetCommit() string { - if m != nil { - return m.Commit - } - return "" -} - -var fileDescriptorInternal = []byte{ - // 1586 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x5f, 0x8f, 0xdb, 0x44, - 0x10, 0x97, 0x93, 0x38, 0x89, 0x27, 0xd7, 0xe3, 0x64, 0x4e, 0xad, 0x29, 0x12, 0x0a, 0x16, 0x7f, - 0xc2, 0x9f, 0x1e, 0x55, 0x2a, 0xa4, 0xaa, 0x82, 0x4a, 0xb9, 0x0b, 0x2d, 0x47, 0xaf, 0xbd, 0xeb, - 0xe6, 0xee, 0x78, 0x42, 0xd5, 0x26, 0x99, 0x24, 0x56, 0x1d, 0xdb, 0xac, 0xed, 0xbb, 0x98, 0x8f, - 0xc0, 0x87, 0x40, 0x42, 0x82, 0x2f, 0x80, 0x78, 0xe1, 0x89, 0x77, 0x3e, 0x08, 0x5f, 0x01, 0x1e, - 0xd1, 0xec, 0xae, 0x1d, 0xe7, 0x92, 0x56, 0x45, 0x42, 0xbc, 0xed, 0x6f, 0x66, 0x3c, 0xbb, 0xf3, - 0x7f, 0x0c, 0xdb, 0x5e, 0x90, 0xa0, 0x08, 0xb8, 0xbf, 0x17, 0x89, 0x30, 0x09, 0xed, 0x66, 0x8e, - 0xdd, 0x3f, 0x2b, 0x50, 0x1f, 0x84, 0xa9, 0x18, 0xa1, 0xbd, 0x0d, 0x95, 0xc3, 0xbe, 0x63, 0xb4, - 0x8d, 0x4e, 0x95, 0x55, 0x0e, 0xfb, 0xb6, 0x0d, 0xb5, 0x27, 0x7c, 0x8e, 0x4e, 0xa5, 0x6d, 0x74, - 0x2c, 0x26, 0xcf, 0x44, 0x3b, 0xcd, 0x22, 0x74, 0xaa, 0x8a, 0x46, 0x67, 0xfb, 0x26, 0x34, 0xcf, - 0x62, 0xd2, 0x36, 0x47, 0xa7, 0x26, 0xe9, 0x05, 0x26, 0xde, 0x09, 0x8f, 0xe3, 0xcb, 0x50, 0x8c, - 0x1d, 0x53, 0xf1, 0x72, 0x6c, 0xef, 0x40, 0xf5, 0x8c, 0x1d, 0x39, 0x75, 0x49, 0xa6, 0xa3, 0xed, - 0x40, 0xa3, 0x8f, 0x13, 0x9e, 0xfa, 0x89, 0xd3, 0x68, 0x1b, 0x9d, 0x26, 0xcb, 0x21, 0xe9, 0x39, - 0x45, 0x1f, 0xa7, 0x82, 0x4f, 0x9c, 0xa6, 0xd2, 0x93, 0x63, 0x7b, 0x0f, 0xec, 0xc3, 0x20, 0xc6, - 0x51, 0x2a, 0x70, 0xf0, 0xdc, 0x8b, 0xce, 0x51, 0x78, 0x93, 0xcc, 0xb1, 0xa4, 0x82, 0x0d, 0x1c, - 0xba, 0xe5, 0x31, 0x26, 0x9c, 0xee, 0x06, 0xa9, 0x2a, 0x87, 0xb6, 0x0b, 0x5b, 0x83, 0x19, 0x17, - 0x38, 0x1e, 0xe0, 0x48, 0x60, 0xe2, 0xb4, 0x24, 0x7b, 0x85, 0x46, 0x32, 0xc7, 0x62, 0xca, 0x03, - 0xef, 0x3b, 0x9e, 0x78, 0x61, 0xe0, 0x6c, 0x29, 0x99, 0x32, 0x8d, 0xbc, 0xc4, 0x42, 0x1f, 0x9d, - 0x6b, 0xca, 0x4b, 0x74, 0x76, 0x7f, 0x35, 0xc0, 0xea, 0xf3, 0x78, 0x36, 0x0c, 0xb9, 0x18, 0xbf, - 0x92, 0xaf, 0x6f, 0x81, 0x39, 0x42, 0xdf, 0x8f, 0x9d, 0x6a, 0xbb, 0xda, 0x69, 0x75, 0x6f, 0xec, - 0x15, 0x41, 0x2c, 0xf4, 0x1c, 0xa0, 0xef, 0x33, 0x25, 0x65, 0xdf, 0x06, 0x2b, 0xc1, 0x79, 0xe4, - 0xf3, 0x04, 0x63, 0xa7, 0x26, 0x3f, 0xb1, 0x97, 0x9f, 0x9c, 0x6a, 0x16, 0x5b, 0x0a, 0xad, 0x99, - 0x62, 0xae, 0x9b, 0xe2, 0xfe, 0x56, 0x85, 0x6b, 0x2b, 0xd7, 0xd9, 0x5b, 0x60, 0x2c, 0xe4, 0xcb, - 0x4d, 0x66, 0x2c, 0x08, 0x65, 0xf2, 0xd5, 0x26, 0x33, 0x32, 0x42, 0x97, 0x32, 0x37, 0x4c, 0x66, - 0x5c, 0x12, 0x9a, 0xc9, 0x8c, 0x30, 0x99, 0x31, 0xb3, 0x3f, 0x80, 0xc6, 0xb7, 0x29, 0x0a, 0x0f, - 0x63, 0xc7, 0x94, 0xaf, 0x7b, 0x6d, 0xf9, 0xba, 0xa7, 0x29, 0x8a, 0x8c, 0xe5, 0x7c, 0xf2, 0x86, - 0xcc, 0x26, 0x95, 0x1a, 0xf2, 0x4c, 0xb4, 0x84, 0x32, 0xaf, 0xa1, 0x68, 0x74, 0xd6, 0x5e, 0x54, - 0xf9, 0x40, 0x5e, 0xfc, 0x14, 0x6a, 0x7c, 0x81, 0xb1, 0x63, 0x49, 0xfd, 0x6f, 0xbf, 0xc0, 0x61, - 0x7b, 0xbd, 0x05, 0xc6, 0x5f, 0x04, 0x89, 0xc8, 0x98, 0x14, 0xb7, 0xdf, 0x87, 0xfa, 0x28, 0xf4, - 0x43, 0x11, 0x3b, 0x70, 0xf5, 0x61, 0x07, 0x44, 0x67, 0x9a, 0x6d, 0x77, 0xa0, 0xee, 0xe3, 0x14, - 0x83, 0xb1, 0xcc, 0x8c, 0x56, 0x77, 0x67, 0x29, 0x78, 0x24, 0xe9, 0x4c, 0xf3, 0xed, 0x7b, 0xb0, - 0x95, 0xf0, 0xa1, 0x8f, 0xc7, 0x11, 0x79, 0x31, 0x96, 0x59, 0xd2, 0xea, 0x5e, 0x2f, 0xc5, 0xa3, - 0xc4, 0x65, 0x2b, 0xb2, 0x37, 0x1f, 0x82, 0x55, 0xbc, 0x90, 0x8a, 0xe4, 0x39, 0x66, 0xd2, 0xdf, - 0x16, 0xa3, 0xa3, 0xfd, 0x0e, 0x98, 0x17, 0xdc, 0x4f, 0x55, 0xae, 0xb4, 0xba, 0xdb, 0x4b, 0x9d, - 0xbd, 0x85, 0x17, 0x33, 0xc5, 0xbc, 0x57, 0xb9, 0x6b, 0xb8, 0xdf, 0x57, 0x60, 0xab, 0x7c, 0x8f, - 0xfd, 0x16, 0x40, 0xe2, 0xcd, 0xf1, 0x41, 0x28, 0xe6, 0x3c, 0xd1, 0x3a, 0x4b, 0x14, 0xfb, 0x43, - 0xd8, 0xb9, 0x40, 0x91, 0x78, 0x23, 0xee, 0x9f, 0x7a, 0x73, 0x24, 0x7d, 0xf2, 0x96, 0x26, 0x5b, - 0xa3, 0xdb, 0xb7, 0xa1, 0x1e, 0x87, 0x22, 0xd9, 0xcf, 0x64, 0xbc, 0x5b, 0x5d, 0x67, 0xf9, 0x0e, - 0x86, 0x01, 0x9f, 0xd3, 0xbd, 0x0f, 0x3c, 0xf4, 0xc7, 0x4c, 0xcb, 0x51, 0x0d, 0x5f, 0x0a, 0x1e, - 0x45, 0x5e, 0x30, 0xcd, 0xfb, 0x44, 0x8e, 0xed, 0xbb, 0x00, 0x13, 0x12, 0xa6, 0xc4, 0xcf, 0xf3, - 0xe3, 0xc5, 0x1a, 0x4b, 0xb2, 0xf6, 0x7b, 0xb0, 0x3d, 0xf1, 0x16, 0x0f, 0x3c, 0x11, 0x27, 0x07, - 0xa1, 0x9f, 0xce, 0x03, 0x99, 0x35, 0x4d, 0x76, 0x85, 0xea, 0x46, 0xb0, 0xbd, 0xaa, 0x85, 0xd2, - 0x3f, 0xbf, 0x40, 0xd6, 0x9e, 0xf2, 0xc7, 0x0a, 0xcd, 0x6e, 0x43, 0x6b, 0xec, 0xc5, 0x91, 0xcf, - 0xb3, 0x52, 0x79, 0x96, 0x49, 0xd4, 0x4d, 0x2e, 0xbc, 0xd8, 0x1b, 0xfa, 0xaa, 0x29, 0x36, 0x59, - 0x0e, 0xdd, 0x29, 0x98, 0x32, 0x7d, 0x4a, 0xc5, 0x6e, 0xe5, 0xc5, 0x2e, 0x9b, 0x68, 0xa5, 0xd4, - 0x44, 0x77, 0xa0, 0xfa, 0x25, 0x2e, 0x74, 0x5f, 0xa5, 0x63, 0xd1, 0x12, 0x6a, 0xa5, 0x96, 0xb0, - 0x0b, 0xe6, 0xb9, 0x8c, 0xbd, 0x2a, 0x55, 0x05, 0xdc, 0xfb, 0x50, 0x57, 0xe9, 0x57, 0x68, 0x36, - 0x4a, 0x9a, 0xdb, 0xd0, 0x3a, 0x16, 0x1e, 0x06, 0x89, 0x2a, 0x72, 0x6d, 0x42, 0x89, 0xe4, 0xfe, - 0x62, 0x40, 0x4d, 0xc6, 0xd4, 0x85, 0x2d, 0x1f, 0xa7, 0x7c, 0x94, 0xed, 0x87, 0x69, 0x30, 0x8e, - 0x1d, 0xa3, 0x5d, 0xed, 0x54, 0xd9, 0x0a, 0xcd, 0xbe, 0x0e, 0xf5, 0xa1, 0xe2, 0x56, 0xda, 0xd5, - 0x8e, 0xc5, 0x34, 0xa2, 0xa7, 0xf9, 0x7c, 0x88, 0xbe, 0x36, 0x41, 0x01, 0x92, 0x8e, 0x04, 0x4e, - 0xbc, 0x85, 0x36, 0x43, 0x23, 0xa2, 0xc7, 0xe9, 0x84, 0xe8, 0xca, 0x12, 0x8d, 0xc8, 0x80, 0x21, - 0x8f, 0x8b, 0xca, 0xa7, 0x33, 0x69, 0x8e, 0x47, 0xdc, 0xcf, 0x4b, 0x5f, 0x01, 0xf7, 0x77, 0x83, - 0x46, 0x82, 0x6a, 0x65, 0x6b, 0x1e, 0x7e, 0x03, 0x9a, 0xd4, 0xe6, 0x9e, 0x5d, 0x70, 0xa1, 0x0d, - 0x6e, 0x10, 0x3e, 0xe7, 0xc2, 0xfe, 0x04, 0xea, 0xb2, 0x42, 0x36, 0xb4, 0xd5, 0x5c, 0x9d, 0xf4, - 0x2a, 0xd3, 0x62, 0x45, 0xe3, 0xa9, 0x95, 0x1a, 0x4f, 0x61, 0xac, 0x59, 0x36, 0xf6, 0x16, 0x98, - 0xd4, 0xc1, 0x32, 0xf9, 0xfa, 0x8d, 0x9a, 0x55, 0x9f, 0x53, 0x52, 0xee, 0x19, 0x5c, 0x5b, 0xb9, - 0xb1, 0xb8, 0xc9, 0x58, 0xbd, 0x69, 0x59, 0xed, 0x96, 0xae, 0x6e, 0x2a, 0xa5, 0x18, 0x7d, 0x1c, - 0x25, 0x38, 0xd6, 0x59, 0x57, 0x60, 0xf7, 0x47, 0x63, 0xa9, 0x57, 0xde, 0x47, 0x29, 0x3a, 0x0a, - 0xe7, 0x73, 0x1e, 0x8c, 0xb5, 0xea, 0x1c, 0x92, 0xdf, 0xc6, 0x43, 0xad, 0xba, 0x32, 0x1e, 0x12, - 0x16, 0x91, 0x8e, 0x60, 0x45, 0x44, 0x94, 0x3b, 0x73, 0xe4, 0x71, 0x2a, 0x70, 0x8e, 0x41, 0xa2, - 0x5d, 0x50, 0x26, 0xd9, 0x37, 0xa0, 0x91, 0xf0, 0xe9, 0x33, 0xea, 0x51, 0x3a, 0x92, 0x09, 0x9f, - 0x3e, 0xc2, 0xcc, 0x7e, 0x13, 0x2c, 0x59, 0xa5, 0x92, 0xa5, 0xc2, 0xd9, 0x94, 0x84, 0x47, 0x98, - 0xb9, 0x7f, 0x1b, 0x50, 0x1f, 0xa0, 0xb8, 0x40, 0xf1, 0x4a, 0x93, 0xb0, 0xbc, 0x61, 0x54, 0x5f, - 0xb2, 0x61, 0xd4, 0x36, 0x6f, 0x18, 0xe6, 0x72, 0xc3, 0xd8, 0x05, 0x73, 0x20, 0x46, 0x87, 0x7d, - 0xf9, 0xa2, 0x2a, 0x53, 0x80, 0xb2, 0xb1, 0x37, 0x4a, 0xbc, 0x0b, 0xd4, 0x6b, 0x87, 0x46, 0x6b, - 0x03, 0xb2, 0xb9, 0x61, 0xd6, 0xff, 0xcb, 0xed, 0xc3, 0xfd, 0xc1, 0x80, 0xfa, 0x11, 0xcf, 0xc2, - 0x34, 0x59, 0xcb, 0xda, 0x36, 0xb4, 0x7a, 0x51, 0xe4, 0x7b, 0xa3, 0x95, 0x4a, 0x2d, 0x91, 0x48, - 0xe2, 0x71, 0x29, 0x1e, 0xca, 0x17, 0x65, 0x12, 0x4d, 0x87, 0x03, 0xb9, 0x34, 0xa8, 0x0d, 0xa0, - 0x34, 0x1d, 0xd4, 0xae, 0x20, 0x99, 0xe4, 0xb4, 0x5e, 0x9a, 0x84, 0x13, 0x3f, 0xbc, 0x94, 0xde, - 0x69, 0xb2, 0x02, 0xbb, 0x7f, 0x54, 0xa0, 0xf6, 0x7f, 0x0d, 0xfa, 0x2d, 0x30, 0x3c, 0x9d, 0x1c, - 0x86, 0x57, 0x8c, 0xfd, 0x46, 0x69, 0xec, 0x3b, 0xd0, 0xc8, 0x04, 0x0f, 0xa6, 0x18, 0x3b, 0x4d, - 0xd9, 0x8d, 0x72, 0x28, 0x39, 0xb2, 0xee, 0xd4, 0xbc, 0xb7, 0x58, 0x0e, 0x8b, 0x3a, 0x82, 0x52, - 0x1d, 0x7d, 0xac, 0x57, 0x83, 0xd6, 0xd5, 0xd1, 0xb2, 0x69, 0x23, 0xf8, 0xef, 0x46, 0xf0, 0x5f, - 0x06, 0x98, 0x45, 0x11, 0x1e, 0xac, 0x16, 0xe1, 0xc1, 0xb2, 0x08, 0xfb, 0xfb, 0x79, 0x11, 0xf6, - 0xf7, 0x09, 0xb3, 0x93, 0xbc, 0x08, 0xd9, 0x09, 0x05, 0xeb, 0xa1, 0x08, 0xd3, 0x68, 0x3f, 0x53, - 0x51, 0xb5, 0x58, 0x81, 0x29, 0x73, 0xbf, 0x9e, 0xa1, 0xd0, 0xae, 0xb6, 0x98, 0x46, 0x94, 0xe7, - 0x47, 0xb2, 0x41, 0x29, 0xe7, 0x2a, 0x60, 0xbf, 0x0b, 0x26, 0x23, 0xe7, 0x49, 0x0f, 0xaf, 0xc4, - 0x45, 0x92, 0x99, 0xe2, 0x92, 0x52, 0xf5, 0x4b, 0xa0, 0x13, 0x3e, 0xff, 0x41, 0xf8, 0x08, 0xea, - 0x83, 0x99, 0x37, 0x49, 0xf2, 0x05, 0xeb, 0xf5, 0x52, 0x83, 0xf3, 0xe6, 0x28, 0x79, 0x4c, 0x8b, - 0xb8, 0x4f, 0xc1, 0x2a, 0x88, 0xcb, 0xe7, 0x18, 0xe5, 0xe7, 0xd8, 0x50, 0x3b, 0x0b, 0xbc, 0x24, - 0x2f, 0x75, 0x3a, 0x93, 0xb1, 0x4f, 0x53, 0x1e, 0x24, 0x5e, 0x92, 0xe5, 0xa5, 0x9e, 0x63, 0xf7, - 0x8e, 0x7e, 0x3e, 0xa9, 0x3b, 0x8b, 0x22, 0x14, 0xba, 0x6d, 0x28, 0x20, 0x2f, 0x09, 0x2f, 0x51, - 0x75, 0xfc, 0x2a, 0x53, 0xc0, 0xfd, 0x06, 0xac, 0x9e, 0x8f, 0x22, 0x61, 0xa9, 0x8f, 0x9b, 0x26, - 0xf1, 0x57, 0x83, 0xe3, 0x27, 0xf9, 0x0b, 0xe8, 0xbc, 0x6c, 0x11, 0xd5, 0x2b, 0x2d, 0xe2, 0x11, - 0x8f, 0xf8, 0x61, 0x5f, 0xe6, 0x79, 0x95, 0x69, 0xe4, 0xfe, 0x64, 0x40, 0x8d, 0x7a, 0x51, 0x49, - 0x75, 0xed, 0x65, 0x7d, 0xec, 0x44, 0x84, 0x17, 0xde, 0x18, 0x45, 0x6e, 0x5c, 0x8e, 0xa5, 0xd3, - 0x47, 0x33, 0x2c, 0x06, 0xbe, 0x46, 0x94, 0x6b, 0xf4, 0xff, 0x90, 0xd7, 0x52, 0x29, 0xd7, 0x88, - 0xcc, 0x14, 0x93, 0x36, 0xbb, 0x41, 0x1a, 0xa1, 0xe8, 0x8d, 0xe7, 0x5e, 0xbe, 0x01, 0x95, 0x28, - 0xee, 0x7d, 0xf5, 0x47, 0xb2, 0xd6, 0xd1, 0x8c, 0xcd, 0x7f, 0x2f, 0x57, 0x5f, 0xee, 0xfe, 0x6c, - 0x40, 0xe3, 0xb1, 0xde, 0xd5, 0xca, 0x56, 0x18, 0x2f, 0xb4, 0xa2, 0xb2, 0x62, 0x45, 0x17, 0x76, - 0x73, 0x99, 0x95, 0xfb, 0x95, 0x17, 0x36, 0xf2, 0xb4, 0x47, 0x6b, 0x45, 0xb0, 0x5e, 0xe5, 0x77, - 0xe5, 0x74, 0x55, 0x66, 0x53, 0xc0, 0xd7, 0xa2, 0xd2, 0x86, 0x96, 0xfe, 0xcd, 0x94, 0x3f, 0x6d, - 0xba, 0xa9, 0x96, 0x48, 0x6e, 0x17, 0xea, 0x07, 0x61, 0x30, 0xf1, 0xa6, 0x76, 0x07, 0x6a, 0xbd, - 0x34, 0x99, 0x49, 0x8d, 0xad, 0xee, 0x6e, 0xa9, 0xf0, 0xd3, 0x64, 0xa6, 0x64, 0x98, 0x94, 0x70, - 0x3f, 0x03, 0x58, 0xd2, 0x68, 0x4a, 0x2c, 0xa3, 0xf1, 0x04, 0x2f, 0x29, 0x65, 0x62, 0xa9, 0xa5, - 0xc9, 0x36, 0x70, 0xdc, 0xcf, 0xc1, 0xda, 0x4f, 0x3d, 0x7f, 0x7c, 0x18, 0x4c, 0x42, 0x6a, 0x1d, - 0xe7, 0x28, 0xe2, 0x65, 0xbc, 0x72, 0x48, 0xee, 0xa6, 0x2e, 0x52, 0xd4, 0x90, 0x46, 0xc3, 0xba, - 0xfc, 0xcd, 0xbf, 0xf3, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xda, 0x7c, 0x0d, 0xab, 0xf8, 0x0f, - 0x00, 0x00, -} diff --git a/chronograf/bolt/client.go b/chronograf/bolt/client.go deleted file mode 100644 index 481e8a9cbd7..00000000000 --- a/chronograf/bolt/client.go +++ /dev/null @@ -1,278 +0,0 @@ -package bolt - -import ( - "context" - "fmt" - "io" - "os" - "path" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/id" - bolt "go.etcd.io/bbolt" -) - -// Client is a client for the boltDB data store. -type Client struct { - Path string - db *bolt.DB - logger chronograf.Logger - isNew bool - Now func() time.Time - LayoutIDs chronograf.ID - - BuildStore *BuildStore - SourcesStore *SourcesStore - ServersStore *ServersStore - LayoutsStore *LayoutsStore - DashboardsStore *DashboardsStore - UsersStore *UsersStore - OrganizationsStore *OrganizationsStore - ConfigStore *ConfigStore - MappingsStore *MappingsStore - OrganizationConfigStore *OrganizationConfigStore -} - -// NewClient initializes all stores -func NewClient() *Client { - c := &Client{Now: time.Now} - c.BuildStore = &BuildStore{client: c} - c.SourcesStore = &SourcesStore{client: c} - c.ServersStore = &ServersStore{client: c} - c.LayoutsStore = &LayoutsStore{ - client: c, - IDs: &id.UUID{}, - } - c.DashboardsStore = &DashboardsStore{ - client: c, - IDs: &id.UUID{}, - } - c.UsersStore = &UsersStore{client: c} - c.OrganizationsStore = &OrganizationsStore{client: c} - c.ConfigStore = &ConfigStore{client: c} - c.MappingsStore = &MappingsStore{client: c} - c.OrganizationConfigStore = &OrganizationConfigStore{client: c} - return c -} - -// WithDB sets the boltdb database for a client. It should not be called -// after a call to Open. -func (c *Client) WithDB(db *bolt.DB) { - c.db = db -} - -// Option to change behavior of Open() -type Option interface { - Backup() bool -} - -// WithBackup returns a Backup -func WithBackup() Option { - return Backup{} -} - -// Backup tells Open to perform a backup prior to initialization -type Backup struct { -} - -// Backup returns true -func (b Backup) Backup() bool { - return true -} - -// Open / create boltDB file. -func (c *Client) Open(ctx context.Context, logger chronograf.Logger, build chronograf.BuildInfo, opts ...Option) error { - if c.db == nil { - if _, err := os.Stat(c.Path); os.IsNotExist(err) { - c.isNew = true - } else if err != nil { - return err - } - - // Open database file. - db, err := bolt.Open(c.Path, 0600, &bolt.Options{Timeout: 1 * time.Second}) - if err != nil { - return fmt.Errorf("unable to open boltdb; is there a chronograf already running? %v", err) - } - c.db = db - c.logger = logger - - for _, opt := range opts { - if opt.Backup() { - if err = c.backup(ctx, build); err != nil { - return fmt.Errorf("unable to backup your database prior to migrations: %v", err) - } - } - } - } - - if err := c.initialize(ctx); err != nil { - return fmt.Errorf("unable to boot boltdb: %v", err) - } - if err := c.migrate(ctx, build); err != nil { - return fmt.Errorf("unable to migrate boltdb: %v", err) - } - - return nil -} - -// initialize creates Buckets that are missing -func (c *Client) initialize(ctx context.Context) error { - if err := c.db.Update(func(tx *bolt.Tx) error { - // Always create SchemaVersions bucket. - if _, err := tx.CreateBucketIfNotExists(SchemaVersionBucket); err != nil { - return err - } - // Always create Organizations bucket. - if _, err := tx.CreateBucketIfNotExists(OrganizationsBucket); err != nil { - return err - } - // Always create Sources bucket. - if _, err := tx.CreateBucketIfNotExists(SourcesBucket); err != nil { - return err - } - // Always create Servers bucket. - if _, err := tx.CreateBucketIfNotExists(ServersBucket); err != nil { - return err - } - // Always create Layouts bucket. - if _, err := tx.CreateBucketIfNotExists(LayoutsBucket); err != nil { - return err - } - // Always create Dashboards bucket. - if _, err := tx.CreateBucketIfNotExists(DashboardsBucket); err != nil { - return err - } - // Always create Users bucket. - if _, err := tx.CreateBucketIfNotExists(UsersBucket); err != nil { - return err - } - // Always create Config bucket. - if _, err := tx.CreateBucketIfNotExists(ConfigBucket); err != nil { - return err - } - // Always create Build bucket. - if _, err := tx.CreateBucketIfNotExists(BuildBucket); err != nil { - return err - } - // Always create Mapping bucket. - if _, err := tx.CreateBucketIfNotExists(MappingsBucket); err != nil { - return err - } - // Always create OrganizationConfig bucket. - if _, err := tx.CreateBucketIfNotExists(OrganizationConfigBucket); err != nil { - return err - } - return nil - }); err != nil { - return err - } - - return nil -} - -// migrate moves data from an old schema to a new schema in each Store -func (c *Client) migrate(ctx context.Context, build chronograf.BuildInfo) error { - if c.db != nil { - // Runtime migrations - if err := c.OrganizationsStore.Migrate(ctx); err != nil { - return err - } - if err := c.SourcesStore.Migrate(ctx); err != nil { - return err - } - if err := c.ServersStore.Migrate(ctx); err != nil { - return err - } - if err := c.LayoutsStore.Migrate(ctx); err != nil { - return err - } - if err := c.DashboardsStore.Migrate(ctx); err != nil { - return err - } - if err := c.ConfigStore.Migrate(ctx); err != nil { - return err - } - if err := c.BuildStore.Migrate(ctx, build); err != nil { - return err - } - if err := c.MappingsStore.Migrate(ctx); err != nil { - return err - } - if err := c.OrganizationConfigStore.Migrate(ctx); err != nil { - return err - } - - MigrateAll(c) - } - return nil -} - -// Close the connection to the bolt database -func (c *Client) Close() error { - if c.db != nil { - return c.db.Close() - } - return nil -} - -// copy creates a copy of the database in toFile -func (c *Client) copy(ctx context.Context, version string) error { - backupDir := path.Join(path.Dir(c.Path), "backup") - if _, err := os.Stat(backupDir); os.IsNotExist(err) { - if err = os.Mkdir(backupDir, 0700); err != nil { - return err - } - } else if err != nil { - return err - } - - fromFile, err := os.Open(c.Path) - if err != nil { - return err - } - defer fromFile.Close() - - toName := fmt.Sprintf("%s.%s", path.Base(c.Path), version) - toPath := path.Join(backupDir, toName) - toFile, err := os.OpenFile(toPath, os.O_RDWR|os.O_CREATE, 0600) - if err != nil { - return err - } - defer toFile.Close() - - _, err = io.Copy(toFile, fromFile) - if err != nil { - return err - } - - c.logger.Info("Successfully created ", toPath) - - return nil -} - -// backup makes a copy of the database to the backup/ directory, if necessary: -// - If this is a fresh install, don't create a backup and store the current version -// - If we are on the same version, don't create a backup -// - If the version has changed, create a backup and store the current version -func (c *Client) backup(ctx context.Context, build chronograf.BuildInfo) error { - lastBuild, err := c.BuildStore.Get(ctx) - if err != nil { - return err - } - if lastBuild.Version == build.Version { - return nil - } - if c.isNew { - return nil - } - - // The database was pre-existing, and the version has changed - // and so create a backup - - c.logger.Info("Moving from version ", lastBuild.Version) - c.logger.Info("Moving to version ", build.Version) - - return c.copy(ctx, lastBuild.Version) -} diff --git a/chronograf/bolt/config.go b/chronograf/bolt/config.go deleted file mode 100644 index fd3043edfa5..00000000000 --- a/chronograf/bolt/config.go +++ /dev/null @@ -1,71 +0,0 @@ -package bolt - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - bolt "go.etcd.io/bbolt" -) - -// Ensure ConfigStore implements chronograf.ConfigStore. -var _ chronograf.ConfigStore = &ConfigStore{} - -// ConfigBucket is used to store chronograf application state -var ConfigBucket = []byte("ConfigV1") - -// configID is the boltDB key where the configuration object is stored -var configID = []byte("config/v1") - -// ConfigStore uses bolt to store and retrieve global -// application configuration -type ConfigStore struct { - client *Client -} - -func (s *ConfigStore) Migrate(ctx context.Context) error { - if _, err := s.Get(ctx); err != nil { - return s.Initialize(ctx) - } - return nil -} - -func (s *ConfigStore) Initialize(ctx context.Context) error { - cfg := chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - } - return s.Update(ctx, &cfg) -} - -func (s *ConfigStore) Get(ctx context.Context) (*chronograf.Config, error) { - var cfg chronograf.Config - err := s.client.db.View(func(tx *bolt.Tx) error { - v := tx.Bucket(ConfigBucket).Get(configID) - if v == nil { - return chronograf.ErrConfigNotFound - } - return internal.UnmarshalConfig(v, &cfg) - }) - - if err != nil { - return nil, err - } - return &cfg, nil -} - -func (s *ConfigStore) Update(ctx context.Context, cfg *chronograf.Config) error { - if cfg == nil { - return fmt.Errorf("config provided was nil") - } - return s.client.db.Update(func(tx *bolt.Tx) error { - if v, err := internal.MarshalConfig(cfg); err != nil { - return err - } else if err := tx.Bucket(ConfigBucket).Put(configID, v); err != nil { - return err - } - return nil - }) -} diff --git a/chronograf/bolt/config_test.go b/chronograf/bolt/config_test.go deleted file mode 100644 index 3982bfe0bfa..00000000000 --- a/chronograf/bolt/config_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package bolt_test - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestConfig_Get(t *testing.T) { - type wants struct { - config *chronograf.Config - err error - } - tests := []struct { - name string - wants wants - }{ - { - name: "Get config", - wants: wants{ - config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.ConfigStore - got, err := s.Get(context.Background()) - if (tt.wants.err != nil) != (err != nil) { - t.Errorf("%q. ConfigStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) - continue - } - if diff := cmp.Diff(got, tt.wants.config); diff != "" { - t.Errorf("%q. ConfigStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestConfig_Update(t *testing.T) { - type args struct { - config *chronograf.Config - } - type wants struct { - config *chronograf.Config - err error - } - tests := []struct { - name string - args args - wants wants - }{ - { - name: "Set config", - args: args{ - config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - wants: wants{ - config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.ConfigStore - err = s.Update(context.Background(), tt.args.config) - if (tt.wants.err != nil) != (err != nil) { - t.Errorf("%q. ConfigStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) - continue - } - - got, _ := s.Get(context.Background()) - if (tt.wants.err != nil) != (err != nil) { - t.Errorf("%q. ConfigStore.Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) - continue - } - - if diff := cmp.Diff(got, tt.wants.config); diff != "" { - t.Errorf("%q. ConfigStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} diff --git a/chronograf/bolt/dashboards.go b/chronograf/bolt/dashboards.go deleted file mode 100644 index b37cf8f0872..00000000000 --- a/chronograf/bolt/dashboards.go +++ /dev/null @@ -1,194 +0,0 @@ -package bolt - -import ( - "context" - "strconv" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - bolt "go.etcd.io/bbolt" -) - -// Ensure DashboardsStore implements chronograf.DashboardsStore. -var _ chronograf.DashboardsStore = &DashboardsStore{} - -// DashboardsBucket is the bolt bucket dashboards are stored in -var DashboardsBucket = []byte("Dashoard") // N.B. leave the misspelling for backwards-compat! - -// DashboardsStore is the bolt implementation of storing dashboards -type DashboardsStore struct { - client *Client - IDs chronograf.ID -} - -// AddIDs is a migration function that adds ID information to existing dashboards -func (d *DashboardsStore) AddIDs(ctx context.Context, boards []chronograf.Dashboard) error { - for _, board := range boards { - update := false - for i, cell := range board.Cells { - // If there are is no id set, we generate one and update the dashboard - if cell.ID == "" { - id, err := d.IDs.Generate() - if err != nil { - return err - } - cell.ID = id - board.Cells[i] = cell - update = true - } - } - if !update { - continue - } - if err := d.Update(ctx, board); err != nil { - return err - } - } - return nil -} - -// Migrate updates the dashboards at runtime -func (d *DashboardsStore) Migrate(ctx context.Context) error { - // 1. Add UUIDs to cells without one - boards, err := d.All(ctx) - if err != nil { - return err - } - if err := d.AddIDs(ctx, boards); err != nil { - return nil - } - - defaultOrg, err := d.client.OrganizationsStore.DefaultOrganization(ctx) - if err != nil { - return err - } - - for _, board := range boards { - if board.Organization == "" { - board.Organization = defaultOrg.ID - if err := d.Update(ctx, board); err != nil { - return nil - } - } - } - - return nil -} - -// All returns all known dashboards -func (d *DashboardsStore) All(ctx context.Context) ([]chronograf.Dashboard, error) { - var srcs []chronograf.Dashboard - if err := d.client.db.View(func(tx *bolt.Tx) error { - if err := tx.Bucket(DashboardsBucket).ForEach(func(k, v []byte) error { - var src chronograf.Dashboard - if err := internal.UnmarshalDashboard(v, &src); err != nil { - return err - } - srcs = append(srcs, src) - return nil - }); err != nil { - return err - } - return nil - }); err != nil { - return nil, err - } - - return srcs, nil -} - -// Add creates a new Dashboard in the DashboardsStore -func (d *DashboardsStore) Add(ctx context.Context, src chronograf.Dashboard) (chronograf.Dashboard, error) { - if err := d.client.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(DashboardsBucket) - id, _ := b.NextSequence() - - src.ID = chronograf.DashboardID(id) - // TODO: use FormatInt - strID := strconv.Itoa(int(id)) - for i, cell := range src.Cells { - cid, err := d.IDs.Generate() - if err != nil { - return err - } - cell.ID = cid - src.Cells[i] = cell - } - v, err := internal.MarshalDashboard(src) - if err != nil { - return err - } - return b.Put([]byte(strID), v) - }); err != nil { - return chronograf.Dashboard{}, err - } - - return src, nil -} - -// Get returns a Dashboard if the id exists. -func (d *DashboardsStore) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - var src chronograf.Dashboard - if err := d.client.db.View(func(tx *bolt.Tx) error { - strID := strconv.Itoa(int(id)) - if v := tx.Bucket(DashboardsBucket).Get([]byte(strID)); v == nil { - return chronograf.ErrDashboardNotFound - } else if err := internal.UnmarshalDashboard(v, &src); err != nil { - return err - } - return nil - }); err != nil { - return chronograf.Dashboard{}, err - } - - return src, nil -} - -// Delete the dashboard from DashboardsStore -func (d *DashboardsStore) Delete(ctx context.Context, dash chronograf.Dashboard) error { - if err := d.client.db.Update(func(tx *bolt.Tx) error { - strID := strconv.Itoa(int(dash.ID)) - if err := tx.Bucket(DashboardsBucket).Delete([]byte(strID)); err != nil { - return err - } - return nil - }); err != nil { - return err - } - - return nil -} - -// Update the dashboard in DashboardsStore -func (d *DashboardsStore) Update(ctx context.Context, dash chronograf.Dashboard) error { - if err := d.client.db.Update(func(tx *bolt.Tx) error { - // Get an existing dashboard with the same ID. - b := tx.Bucket(DashboardsBucket) - strID := strconv.Itoa(int(dash.ID)) - if v := b.Get([]byte(strID)); v == nil { - return chronograf.ErrDashboardNotFound - } - - for i, cell := range dash.Cells { - if cell.ID != "" { - continue - } - cid, err := d.IDs.Generate() - if err != nil { - return err - } - cell.ID = cid - dash.Cells[i] = cell - } - if v, err := internal.MarshalDashboard(dash); err != nil { - return err - } else if err := b.Put([]byte(strID), v); err != nil { - return err - } - return nil - }); err != nil { - return err - } - - return nil -} diff --git a/chronograf/bolt/internal/internal.go b/chronograf/bolt/internal/internal.go deleted file mode 100644 index dafc95bd67a..00000000000 --- a/chronograf/bolt/internal/internal.go +++ /dev/null @@ -1,863 +0,0 @@ -package internal - -import ( - "encoding/json" - "fmt" - - "github.com/gogo/protobuf/proto" - "github.com/influxdata/influxdb/v2/chronograf" -) - -//go:generate protoc --plugin ../../../scripts/protoc-gen-gogo --gogo_out=. internal.proto - -// MarshalBuild encodes a build to binary protobuf format. -func MarshalBuild(b chronograf.BuildInfo) ([]byte, error) { - return proto.Marshal(&BuildInfo{ - Version: b.Version, - Commit: b.Commit, - }) -} - -// UnmarshalBuild decodes a build from binary protobuf data. -func UnmarshalBuild(data []byte, b *chronograf.BuildInfo) error { - var pb BuildInfo - if err := proto.Unmarshal(data, &pb); err != nil { - return err - } - - b.Version = pb.Version - b.Commit = pb.Commit - return nil -} - -// MarshalSource encodes a source to binary protobuf format. -func MarshalSource(s chronograf.Source) ([]byte, error) { - return proto.Marshal(&Source{ - ID: int64(s.ID), - Name: s.Name, - Type: s.Type, - Username: s.Username, - Password: s.Password, - SharedSecret: s.SharedSecret, - URL: s.URL, - MetaURL: s.MetaURL, - InsecureSkipVerify: s.InsecureSkipVerify, - Default: s.Default, - Telegraf: s.Telegraf, - Organization: s.Organization, - Role: s.Role, - DefaultRP: s.DefaultRP, - }) -} - -// UnmarshalSource decodes a source from binary protobuf data. -func UnmarshalSource(data []byte, s *chronograf.Source) error { - var pb Source - if err := proto.Unmarshal(data, &pb); err != nil { - return err - } - - s.ID = int(pb.ID) - s.Name = pb.Name - s.Type = pb.Type - s.Username = pb.Username - s.Password = pb.Password - s.SharedSecret = pb.SharedSecret - s.URL = pb.URL - s.MetaURL = pb.MetaURL - s.InsecureSkipVerify = pb.InsecureSkipVerify - s.Default = pb.Default - s.Telegraf = pb.Telegraf - s.Organization = pb.Organization - s.Role = pb.Role - s.DefaultRP = pb.DefaultRP - return nil -} - -// MarshalServer encodes a server to binary protobuf format. -func MarshalServer(s chronograf.Server) ([]byte, error) { - var ( - metadata []byte - err error - ) - metadata, err = json.Marshal(s.Metadata) - if err != nil { - return nil, err - } - return proto.Marshal(&Server{ - ID: int64(s.ID), - SrcID: int64(s.SrcID), - Name: s.Name, - Username: s.Username, - Password: s.Password, - URL: s.URL, - Active: s.Active, - Organization: s.Organization, - InsecureSkipVerify: s.InsecureSkipVerify, - Type: s.Type, - MetadataJSON: string(metadata), - }) -} - -// UnmarshalServer decodes a server from binary protobuf data. -func UnmarshalServer(data []byte, s *chronograf.Server) error { - var pb Server - if err := proto.Unmarshal(data, &pb); err != nil { - return err - } - - s.Metadata = make(map[string]interface{}) - if len(pb.MetadataJSON) > 0 { - if err := json.Unmarshal([]byte(pb.MetadataJSON), &s.Metadata); err != nil { - return err - } - } - - s.ID = int(pb.ID) - s.SrcID = int(pb.SrcID) - s.Name = pb.Name - s.Username = pb.Username - s.Password = pb.Password - s.URL = pb.URL - s.Active = pb.Active - s.Organization = pb.Organization - s.InsecureSkipVerify = pb.InsecureSkipVerify - s.Type = pb.Type - return nil -} - -// MarshalLayout encodes a layout to binary protobuf format. -func MarshalLayout(l chronograf.Layout) ([]byte, error) { - cells := make([]*Cell, len(l.Cells)) - for i, c := range l.Cells { - queries := make([]*Query, len(c.Queries)) - for j, q := range c.Queries { - r := new(Range) - if q.Range != nil { - r.Upper, r.Lower = q.Range.Upper, q.Range.Lower - } - queries[j] = &Query{ - Command: q.Command, - DB: q.DB, - RP: q.RP, - GroupBys: q.GroupBys, - Wheres: q.Wheres, - Label: q.Label, - Range: r, - } - } - - axes := make(map[string]*Axis, len(c.Axes)) - for a, r := range c.Axes { - axes[a] = &Axis{ - Bounds: r.Bounds, - Label: r.Label, - } - } - - cells[i] = &Cell{ - X: c.X, - Y: c.Y, - W: c.W, - H: c.H, - I: c.I, - Name: c.Name, - Queries: queries, - Type: c.Type, - Axes: axes, - } - } - return proto.Marshal(&Layout{ - ID: l.ID, - Measurement: l.Measurement, - Application: l.Application, - Autoflow: l.Autoflow, - Cells: cells, - }) -} - -// UnmarshalLayout decodes a layout from binary protobuf data. -func UnmarshalLayout(data []byte, l *chronograf.Layout) error { - var pb Layout - if err := proto.Unmarshal(data, &pb); err != nil { - return err - } - - l.ID = pb.ID - l.Measurement = pb.Measurement - l.Application = pb.Application - l.Autoflow = pb.Autoflow - cells := make([]chronograf.Cell, len(pb.Cells)) - for i, c := range pb.Cells { - queries := make([]chronograf.Query, len(c.Queries)) - for j, q := range c.Queries { - queries[j] = chronograf.Query{ - Command: q.Command, - DB: q.DB, - RP: q.RP, - GroupBys: q.GroupBys, - Wheres: q.Wheres, - Label: q.Label, - } - if q.Range.Upper != q.Range.Lower { - queries[j].Range = &chronograf.Range{ - Upper: q.Range.Upper, - Lower: q.Range.Lower, - } - } - } - axes := make(map[string]chronograf.Axis, len(c.Axes)) - for a, r := range c.Axes { - axes[a] = chronograf.Axis{ - Bounds: r.Bounds, - Label: r.Label, - } - } - - cells[i] = chronograf.Cell{ - X: c.X, - Y: c.Y, - W: c.W, - H: c.H, - I: c.I, - Name: c.Name, - Queries: queries, - Type: c.Type, - Axes: axes, - } - } - l.Cells = cells - return nil -} - -// MarshalDashboard encodes a dashboard to binary protobuf format. -func MarshalDashboard(d chronograf.Dashboard) ([]byte, error) { - cells := make([]*DashboardCell, len(d.Cells)) - for i, c := range d.Cells { - queries := make([]*Query, len(c.Queries)) - for j, q := range c.Queries { - r := new(Range) - if q.Range != nil { - r.Upper, r.Lower = q.Range.Upper, q.Range.Lower - } - q.Shifts = q.QueryConfig.Shifts - queries[j] = &Query{ - Command: q.Command, - Label: q.Label, - Range: r, - Source: q.Source, - Type: q.Type, - } - - shifts := make([]*TimeShift, len(q.Shifts)) - for k := range q.Shifts { - shift := &TimeShift{ - Label: q.Shifts[k].Label, - Unit: q.Shifts[k].Unit, - Quantity: q.Shifts[k].Quantity, - } - - shifts[k] = shift - } - - queries[j].Shifts = shifts - } - - colors := make([]*Color, len(c.CellColors)) - for j, color := range c.CellColors { - colors[j] = &Color{ - ID: color.ID, - Type: color.Type, - Hex: color.Hex, - Name: color.Name, - Value: color.Value, - } - } - - axes := make(map[string]*Axis, len(c.Axes)) - for a, r := range c.Axes { - axes[a] = &Axis{ - Bounds: r.Bounds, - Label: r.Label, - Prefix: r.Prefix, - Suffix: r.Suffix, - Base: r.Base, - Scale: r.Scale, - } - } - - sortBy := &RenamableField{ - InternalName: c.TableOptions.SortBy.InternalName, - DisplayName: c.TableOptions.SortBy.DisplayName, - Visible: c.TableOptions.SortBy.Visible, - } - - tableOptions := &TableOptions{ - VerticalTimeAxis: c.TableOptions.VerticalTimeAxis, - SortBy: sortBy, - Wrapping: c.TableOptions.Wrapping, - FixFirstColumn: c.TableOptions.FixFirstColumn, - } - - decimalPlaces := &DecimalPlaces{ - IsEnforced: c.DecimalPlaces.IsEnforced, - Digits: c.DecimalPlaces.Digits, - } - - fieldOptions := make([]*RenamableField, len(c.FieldOptions)) - for i, field := range c.FieldOptions { - fieldOptions[i] = &RenamableField{ - InternalName: field.InternalName, - DisplayName: field.DisplayName, - Visible: field.Visible, - } - } - - cells[i] = &DashboardCell{ - ID: c.ID, - X: c.X, - Y: c.Y, - W: c.W, - H: c.H, - Name: c.Name, - Queries: queries, - Type: c.Type, - Axes: axes, - Colors: colors, - Legend: &Legend{ - Type: c.Legend.Type, - Orientation: c.Legend.Orientation, - }, - TableOptions: tableOptions, - FieldOptions: fieldOptions, - TimeFormat: c.TimeFormat, - DecimalPlaces: decimalPlaces, - } - } - templates := make([]*Template, len(d.Templates)) - for i, t := range d.Templates { - vals := make([]*TemplateValue, len(t.Values)) - for j, v := range t.Values { - vals[j] = &TemplateValue{ - Selected: v.Selected, - Type: v.Type, - Value: v.Value, - Key: v.Key, - } - } - - template := &Template{ - ID: string(t.ID), - TempVar: t.Var, - Values: vals, - Type: t.Type, - Label: t.Label, - } - if t.Query != nil { - template.Query = &TemplateQuery{ - Command: t.Query.Command, - Db: t.Query.DB, - Rp: t.Query.RP, - Measurement: t.Query.Measurement, - TagKey: t.Query.TagKey, - FieldKey: t.Query.FieldKey, - } - } - templates[i] = template - } - return proto.Marshal(&Dashboard{ - ID: int64(d.ID), - Cells: cells, - Templates: templates, - Name: d.Name, - Organization: d.Organization, - }) -} - -// UnmarshalDashboard decodes a layout from binary protobuf data. -func UnmarshalDashboard(data []byte, d *chronograf.Dashboard) error { - var pb Dashboard - if err := proto.Unmarshal(data, &pb); err != nil { - return err - } - - cells := make([]chronograf.DashboardCell, len(pb.Cells)) - for i, c := range pb.Cells { - queries := make([]chronograf.DashboardQuery, len(c.Queries)) - for j, q := range c.Queries { - queries[j] = chronograf.DashboardQuery{ - Command: q.Command, - Label: q.Label, - Source: q.Source, - Type: q.Type, - } - - if q.Range.Upper != q.Range.Lower { - queries[j].Range = &chronograf.Range{ - Upper: q.Range.Upper, - Lower: q.Range.Lower, - } - } - - shifts := make([]chronograf.TimeShift, len(q.Shifts)) - for k := range q.Shifts { - shift := chronograf.TimeShift{ - Label: q.Shifts[k].Label, - Unit: q.Shifts[k].Unit, - Quantity: q.Shifts[k].Quantity, - } - - shifts[k] = shift - } - - queries[j].Shifts = shifts - } - - colors := make([]chronograf.CellColor, len(c.Colors)) - for j, color := range c.Colors { - colors[j] = chronograf.CellColor{ - ID: color.ID, - Type: color.Type, - Hex: color.Hex, - Name: color.Name, - Value: color.Value, - } - } - - axes := make(map[string]chronograf.Axis, len(c.Axes)) - for a, r := range c.Axes { - // axis base defaults to 10 - if r.Base == "" { - r.Base = "10" - } - - if r.Scale == "" { - r.Scale = "linear" - } - - if r.Bounds != nil { - axes[a] = chronograf.Axis{ - Bounds: r.Bounds, - Label: r.Label, - Prefix: r.Prefix, - Suffix: r.Suffix, - Base: r.Base, - Scale: r.Scale, - } - - } else { - axes[a] = chronograf.Axis{ - Bounds: []string{}, - Base: r.Base, - Scale: r.Scale, - } - } - } - - legend := chronograf.Legend{} - if c.Legend != nil { - legend.Type = c.Legend.Type - legend.Orientation = c.Legend.Orientation - } - - tableOptions := chronograf.TableOptions{} - if c.TableOptions != nil { - sortBy := chronograf.RenamableField{} - if c.TableOptions.SortBy != nil { - sortBy.InternalName = c.TableOptions.SortBy.InternalName - sortBy.DisplayName = c.TableOptions.SortBy.DisplayName - sortBy.Visible = c.TableOptions.SortBy.Visible - } - tableOptions.SortBy = sortBy - tableOptions.VerticalTimeAxis = c.TableOptions.VerticalTimeAxis - tableOptions.Wrapping = c.TableOptions.Wrapping - tableOptions.FixFirstColumn = c.TableOptions.FixFirstColumn - } - - fieldOptions := make([]chronograf.RenamableField, len(c.FieldOptions)) - for i, field := range c.FieldOptions { - fieldOptions[i] = chronograf.RenamableField{} - fieldOptions[i].InternalName = field.InternalName - fieldOptions[i].DisplayName = field.DisplayName - fieldOptions[i].Visible = field.Visible - } - - decimalPlaces := chronograf.DecimalPlaces{} - if c.DecimalPlaces != nil { - decimalPlaces.IsEnforced = c.DecimalPlaces.IsEnforced - decimalPlaces.Digits = c.DecimalPlaces.Digits - } else { - decimalPlaces.IsEnforced = true - decimalPlaces.Digits = 2 - } - - // FIXME: this is merely for legacy cells and - // should be removed as soon as possible - cellType := c.Type - if cellType == "" { - cellType = "line" - } - - cells[i] = chronograf.DashboardCell{ - ID: c.ID, - X: c.X, - Y: c.Y, - W: c.W, - H: c.H, - Name: c.Name, - Queries: queries, - Type: cellType, - Axes: axes, - CellColors: colors, - Legend: legend, - TableOptions: tableOptions, - FieldOptions: fieldOptions, - TimeFormat: c.TimeFormat, - DecimalPlaces: decimalPlaces, - } - } - - templates := make([]chronograf.Template, len(pb.Templates)) - for i, t := range pb.Templates { - vals := make([]chronograf.TemplateValue, len(t.Values)) - for j, v := range t.Values { - vals[j] = chronograf.TemplateValue{ - Selected: v.Selected, - Type: v.Type, - Value: v.Value, - Key: v.Key, - } - } - - template := chronograf.Template{ - ID: chronograf.TemplateID(t.ID), - TemplateVar: chronograf.TemplateVar{ - Var: t.TempVar, - Values: vals, - }, - Type: t.Type, - Label: t.Label, - } - - if t.Query != nil { - template.Query = &chronograf.TemplateQuery{ - Command: t.Query.Command, - DB: t.Query.Db, - RP: t.Query.Rp, - Measurement: t.Query.Measurement, - TagKey: t.Query.TagKey, - FieldKey: t.Query.FieldKey, - } - } - templates[i] = template - } - - d.ID = chronograf.DashboardID(pb.ID) - d.Cells = cells - d.Templates = templates - d.Name = pb.Name - d.Organization = pb.Organization - return nil -} - -// ScopedAlert contains the source and the kapacitor id -type ScopedAlert struct { - chronograf.AlertRule - SrcID int - KapaID int -} - -// MarshalAlertRule encodes an alert rule to binary protobuf format. -func MarshalAlertRule(r *ScopedAlert) ([]byte, error) { - j, err := json.Marshal(r.AlertRule) - if err != nil { - return nil, err - } - return proto.Marshal(&AlertRule{ - ID: r.ID, - SrcID: int64(r.SrcID), - KapaID: int64(r.KapaID), - JSON: string(j), - }) -} - -// UnmarshalAlertRule decodes an alert rule from binary protobuf data. -func UnmarshalAlertRule(data []byte, r *ScopedAlert) error { - var pb AlertRule - if err := proto.Unmarshal(data, &pb); err != nil { - return err - } - - err := json.Unmarshal([]byte(pb.JSON), &r.AlertRule) - if err != nil { - return err - } - r.SrcID = int(pb.SrcID) - r.KapaID = int(pb.KapaID) - return nil -} - -// MarshalUser encodes a user to binary protobuf format. -// We are ignoring the password for now. -func MarshalUser(u *chronograf.User) ([]byte, error) { - roles := make([]*Role, len(u.Roles)) - for i, role := range u.Roles { - roles[i] = &Role{ - Organization: role.Organization, - Name: role.Name, - } - } - return MarshalUserPB(&User{ - ID: u.ID, - Name: u.Name, - Provider: u.Provider, - Scheme: u.Scheme, - Roles: roles, - SuperAdmin: u.SuperAdmin, - }) -} - -// MarshalUserPB encodes a user to binary protobuf format. -// We are ignoring the password for now. -func MarshalUserPB(u *User) ([]byte, error) { - return proto.Marshal(u) -} - -// UnmarshalUser decodes a user from binary protobuf data. -// We are ignoring the password for now. -func UnmarshalUser(data []byte, u *chronograf.User) error { - var pb User - if err := UnmarshalUserPB(data, &pb); err != nil { - return err - } - roles := make([]chronograf.Role, len(pb.Roles)) - for i, role := range pb.Roles { - roles[i] = chronograf.Role{ - Organization: role.Organization, - Name: role.Name, - } - } - u.ID = pb.ID - u.Name = pb.Name - u.Provider = pb.Provider - u.Scheme = pb.Scheme - u.SuperAdmin = pb.SuperAdmin - u.Roles = roles - - return nil -} - -// UnmarshalUserPB decodes a user from binary protobuf data. -// We are ignoring the password for now. -func UnmarshalUserPB(data []byte, u *User) error { - return proto.Unmarshal(data, u) -} - -// MarshalRole encodes a role to binary protobuf format. -func MarshalRole(r *chronograf.Role) ([]byte, error) { - return MarshalRolePB(&Role{ - Organization: r.Organization, - Name: r.Name, - }) -} - -// MarshalRolePB encodes a role to binary protobuf format. -func MarshalRolePB(r *Role) ([]byte, error) { - return proto.Marshal(r) -} - -// UnmarshalRole decodes a role from binary protobuf data. -func UnmarshalRole(data []byte, r *chronograf.Role) error { - var pb Role - if err := UnmarshalRolePB(data, &pb); err != nil { - return err - } - r.Organization = pb.Organization - r.Name = pb.Name - - return nil -} - -// UnmarshalRolePB decodes a role from binary protobuf data. -func UnmarshalRolePB(data []byte, r *Role) error { - return proto.Unmarshal(data, r) -} - -// MarshalOrganization encodes a organization to binary protobuf format. -func MarshalOrganization(o *chronograf.Organization) ([]byte, error) { - - return MarshalOrganizationPB(&Organization{ - ID: o.ID, - Name: o.Name, - DefaultRole: o.DefaultRole, - }) -} - -// MarshalOrganizationPB encodes a organization to binary protobuf format. -func MarshalOrganizationPB(o *Organization) ([]byte, error) { - return proto.Marshal(o) -} - -// UnmarshalOrganization decodes a organization from binary protobuf data. -func UnmarshalOrganization(data []byte, o *chronograf.Organization) error { - var pb Organization - if err := UnmarshalOrganizationPB(data, &pb); err != nil { - return err - } - o.ID = pb.ID - o.Name = pb.Name - o.DefaultRole = pb.DefaultRole - - return nil -} - -// UnmarshalOrganizationPB decodes a organization from binary protobuf data. -func UnmarshalOrganizationPB(data []byte, o *Organization) error { - return proto.Unmarshal(data, o) -} - -// MarshalConfig encodes a config to binary protobuf format. -func MarshalConfig(c *chronograf.Config) ([]byte, error) { - return MarshalConfigPB(&Config{ - Auth: &AuthConfig{ - SuperAdminNewUsers: c.Auth.SuperAdminNewUsers, - }, - }) -} - -// MarshalConfigPB encodes a config to binary protobuf format. -func MarshalConfigPB(c *Config) ([]byte, error) { - return proto.Marshal(c) -} - -// UnmarshalConfig decodes a config from binary protobuf data. -func UnmarshalConfig(data []byte, c *chronograf.Config) error { - var pb Config - if err := UnmarshalConfigPB(data, &pb); err != nil { - return err - } - if pb.Auth == nil { - return fmt.Errorf("auth config is nil") - } - c.Auth.SuperAdminNewUsers = pb.Auth.SuperAdminNewUsers - - return nil -} - -// UnmarshalConfigPB decodes a config from binary protobuf data. -func UnmarshalConfigPB(data []byte, c *Config) error { - return proto.Unmarshal(data, c) -} - -// MarshalOrganizationConfig encodes a config to binary protobuf format. -func MarshalOrganizationConfig(c *chronograf.OrganizationConfig) ([]byte, error) { - columns := make([]*LogViewerColumn, len(c.LogViewer.Columns)) - - for i, column := range c.LogViewer.Columns { - encodings := make([]*ColumnEncoding, len(column.Encodings)) - - for j, e := range column.Encodings { - encodings[j] = &ColumnEncoding{ - Type: e.Type, - Value: e.Value, - Name: e.Name, - } - } - - columns[i] = &LogViewerColumn{ - Name: column.Name, - Position: column.Position, - Encodings: encodings, - } - } - - return MarshalOrganizationConfigPB(&OrganizationConfig{ - OrganizationID: c.OrganizationID, - LogViewer: &LogViewerConfig{ - Columns: columns, - }, - }) -} - -// MarshalOrganizationConfigPB encodes a config to binary protobuf format. -func MarshalOrganizationConfigPB(c *OrganizationConfig) ([]byte, error) { - return proto.Marshal(c) -} - -// UnmarshalOrganizationConfig decodes a config from binary protobuf data. -func UnmarshalOrganizationConfig(data []byte, c *chronograf.OrganizationConfig) error { - var pb OrganizationConfig - - if err := UnmarshalOrganizationConfigPB(data, &pb); err != nil { - return err - } - - if pb.LogViewer == nil { - return fmt.Errorf("log Viewer config is nil") - } - - c.OrganizationID = pb.OrganizationID - - columns := make([]chronograf.LogViewerColumn, len(pb.LogViewer.Columns)) - - for i, c := range pb.LogViewer.Columns { - columns[i].Name = c.Name - columns[i].Position = c.Position - - encodings := make([]chronograf.ColumnEncoding, len(c.Encodings)) - for j, e := range c.Encodings { - encodings[j].Type = e.Type - encodings[j].Value = e.Value - encodings[j].Name = e.Name - } - - columns[i].Encodings = encodings - } - - c.LogViewer.Columns = columns - - return nil -} - -// UnmarshalOrganizationConfigPB decodes a config from binary protobuf data. -func UnmarshalOrganizationConfigPB(data []byte, c *OrganizationConfig) error { - return proto.Unmarshal(data, c) -} - -// MarshalMapping encodes a mapping to binary protobuf format. -func MarshalMapping(m *chronograf.Mapping) ([]byte, error) { - - return MarshalMappingPB(&Mapping{ - Provider: m.Provider, - Scheme: m.Scheme, - ProviderOrganization: m.ProviderOrganization, - ID: m.ID, - Organization: m.Organization, - }) -} - -// MarshalMappingPB encodes a mapping to binary protobuf format. -func MarshalMappingPB(m *Mapping) ([]byte, error) { - return proto.Marshal(m) -} - -// UnmarshalMapping decodes a mapping from binary protobuf data. -func UnmarshalMapping(data []byte, m *chronograf.Mapping) error { - var pb Mapping - if err := UnmarshalMappingPB(data, &pb); err != nil { - return err - } - - m.Provider = pb.Provider - m.Scheme = pb.Scheme - m.ProviderOrganization = pb.ProviderOrganization - m.Organization = pb.Organization - m.ID = pb.ID - - return nil -} - -// UnmarshalMappingPB decodes a mapping from binary protobuf data. -func UnmarshalMappingPB(data []byte, m *Mapping) error { - return proto.Unmarshal(data, m) -} diff --git a/chronograf/bolt/internal/internal.pb.go b/chronograf/bolt/internal/internal.pb.go deleted file mode 100644 index d79abfc7847..00000000000 --- a/chronograf/bolt/internal/internal.pb.go +++ /dev/null @@ -1,2260 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: internal.proto - -package internal - -import ( - fmt "fmt" - proto "github.com/gogo/protobuf/proto" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -type Source struct { - ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Type string `protobuf:"bytes,3,opt,name=Type,proto3" json:"Type,omitempty"` - Username string `protobuf:"bytes,4,opt,name=Username,proto3" json:"Username,omitempty"` - Password string `protobuf:"bytes,5,opt,name=Password,proto3" json:"Password,omitempty"` - URL string `protobuf:"bytes,6,opt,name=URL,proto3" json:"URL,omitempty"` - Default bool `protobuf:"varint,7,opt,name=Default,proto3" json:"Default,omitempty"` - Telegraf string `protobuf:"bytes,8,opt,name=Telegraf,proto3" json:"Telegraf,omitempty"` - InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"` - MetaURL string `protobuf:"bytes,10,opt,name=MetaURL,proto3" json:"MetaURL,omitempty"` - SharedSecret string `protobuf:"bytes,11,opt,name=SharedSecret,proto3" json:"SharedSecret,omitempty"` - Organization string `protobuf:"bytes,12,opt,name=Organization,proto3" json:"Organization,omitempty"` - Role string `protobuf:"bytes,13,opt,name=Role,proto3" json:"Role,omitempty"` - DefaultRP string `protobuf:"bytes,14,opt,name=DefaultRP,proto3" json:"DefaultRP,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Source) Reset() { *m = Source{} } -func (m *Source) String() string { return proto.CompactTextString(m) } -func (*Source) ProtoMessage() {} -func (*Source) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{0} -} -func (m *Source) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Source.Unmarshal(m, b) -} -func (m *Source) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Source.Marshal(b, m, deterministic) -} -func (m *Source) XXX_Merge(src proto.Message) { - xxx_messageInfo_Source.Merge(m, src) -} -func (m *Source) XXX_Size() int { - return xxx_messageInfo_Source.Size(m) -} -func (m *Source) XXX_DiscardUnknown() { - xxx_messageInfo_Source.DiscardUnknown(m) -} - -var xxx_messageInfo_Source proto.InternalMessageInfo - -func (m *Source) GetID() int64 { - if m != nil { - return m.ID - } - return 0 -} - -func (m *Source) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Source) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Source) GetUsername() string { - if m != nil { - return m.Username - } - return "" -} - -func (m *Source) GetPassword() string { - if m != nil { - return m.Password - } - return "" -} - -func (m *Source) GetURL() string { - if m != nil { - return m.URL - } - return "" -} - -func (m *Source) GetDefault() bool { - if m != nil { - return m.Default - } - return false -} - -func (m *Source) GetTelegraf() string { - if m != nil { - return m.Telegraf - } - return "" -} - -func (m *Source) GetInsecureSkipVerify() bool { - if m != nil { - return m.InsecureSkipVerify - } - return false -} - -func (m *Source) GetMetaURL() string { - if m != nil { - return m.MetaURL - } - return "" -} - -func (m *Source) GetSharedSecret() string { - if m != nil { - return m.SharedSecret - } - return "" -} - -func (m *Source) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -func (m *Source) GetRole() string { - if m != nil { - return m.Role - } - return "" -} - -func (m *Source) GetDefaultRP() string { - if m != nil { - return m.DefaultRP - } - return "" -} - -type Dashboard struct { - ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Cells []*DashboardCell `protobuf:"bytes,3,rep,name=cells,proto3" json:"cells,omitempty"` - Templates []*Template `protobuf:"bytes,4,rep,name=templates,proto3" json:"templates,omitempty"` - Organization string `protobuf:"bytes,5,opt,name=Organization,proto3" json:"Organization,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Dashboard) Reset() { *m = Dashboard{} } -func (m *Dashboard) String() string { return proto.CompactTextString(m) } -func (*Dashboard) ProtoMessage() {} -func (*Dashboard) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{1} -} -func (m *Dashboard) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Dashboard.Unmarshal(m, b) -} -func (m *Dashboard) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Dashboard.Marshal(b, m, deterministic) -} -func (m *Dashboard) XXX_Merge(src proto.Message) { - xxx_messageInfo_Dashboard.Merge(m, src) -} -func (m *Dashboard) XXX_Size() int { - return xxx_messageInfo_Dashboard.Size(m) -} -func (m *Dashboard) XXX_DiscardUnknown() { - xxx_messageInfo_Dashboard.DiscardUnknown(m) -} - -var xxx_messageInfo_Dashboard proto.InternalMessageInfo - -func (m *Dashboard) GetID() int64 { - if m != nil { - return m.ID - } - return 0 -} - -func (m *Dashboard) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Dashboard) GetCells() []*DashboardCell { - if m != nil { - return m.Cells - } - return nil -} - -func (m *Dashboard) GetTemplates() []*Template { - if m != nil { - return m.Templates - } - return nil -} - -func (m *Dashboard) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -type DashboardCell struct { - X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` - Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` - W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"` - H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"` - Queries []*Query `protobuf:"bytes,5,rep,name=queries,proto3" json:"queries,omitempty"` - Name string `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"` - Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` - ID string `protobuf:"bytes,8,opt,name=ID,proto3" json:"ID,omitempty"` - Axes map[string]*Axis `protobuf:"bytes,9,rep,name=axes,proto3" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Colors []*Color `protobuf:"bytes,10,rep,name=colors,proto3" json:"colors,omitempty"` - Legend *Legend `protobuf:"bytes,11,opt,name=legend,proto3" json:"legend,omitempty"` - TableOptions *TableOptions `protobuf:"bytes,12,opt,name=tableOptions,proto3" json:"tableOptions,omitempty"` - FieldOptions []*RenamableField `protobuf:"bytes,13,rep,name=fieldOptions,proto3" json:"fieldOptions,omitempty"` - TimeFormat string `protobuf:"bytes,14,opt,name=timeFormat,proto3" json:"timeFormat,omitempty"` - DecimalPlaces *DecimalPlaces `protobuf:"bytes,15,opt,name=decimalPlaces,proto3" json:"decimalPlaces,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *DashboardCell) Reset() { *m = DashboardCell{} } -func (m *DashboardCell) String() string { return proto.CompactTextString(m) } -func (*DashboardCell) ProtoMessage() {} -func (*DashboardCell) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{2} -} -func (m *DashboardCell) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DashboardCell.Unmarshal(m, b) -} -func (m *DashboardCell) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DashboardCell.Marshal(b, m, deterministic) -} -func (m *DashboardCell) XXX_Merge(src proto.Message) { - xxx_messageInfo_DashboardCell.Merge(m, src) -} -func (m *DashboardCell) XXX_Size() int { - return xxx_messageInfo_DashboardCell.Size(m) -} -func (m *DashboardCell) XXX_DiscardUnknown() { - xxx_messageInfo_DashboardCell.DiscardUnknown(m) -} - -var xxx_messageInfo_DashboardCell proto.InternalMessageInfo - -func (m *DashboardCell) GetX() int32 { - if m != nil { - return m.X - } - return 0 -} - -func (m *DashboardCell) GetY() int32 { - if m != nil { - return m.Y - } - return 0 -} - -func (m *DashboardCell) GetW() int32 { - if m != nil { - return m.W - } - return 0 -} - -func (m *DashboardCell) GetH() int32 { - if m != nil { - return m.H - } - return 0 -} - -func (m *DashboardCell) GetQueries() []*Query { - if m != nil { - return m.Queries - } - return nil -} - -func (m *DashboardCell) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *DashboardCell) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *DashboardCell) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *DashboardCell) GetAxes() map[string]*Axis { - if m != nil { - return m.Axes - } - return nil -} - -func (m *DashboardCell) GetColors() []*Color { - if m != nil { - return m.Colors - } - return nil -} - -func (m *DashboardCell) GetLegend() *Legend { - if m != nil { - return m.Legend - } - return nil -} - -func (m *DashboardCell) GetTableOptions() *TableOptions { - if m != nil { - return m.TableOptions - } - return nil -} - -func (m *DashboardCell) GetFieldOptions() []*RenamableField { - if m != nil { - return m.FieldOptions - } - return nil -} - -func (m *DashboardCell) GetTimeFormat() string { - if m != nil { - return m.TimeFormat - } - return "" -} - -func (m *DashboardCell) GetDecimalPlaces() *DecimalPlaces { - if m != nil { - return m.DecimalPlaces - } - return nil -} - -type DecimalPlaces struct { - IsEnforced bool `protobuf:"varint,1,opt,name=isEnforced,proto3" json:"isEnforced,omitempty"` - Digits int32 `protobuf:"varint,2,opt,name=digits,proto3" json:"digits,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *DecimalPlaces) Reset() { *m = DecimalPlaces{} } -func (m *DecimalPlaces) String() string { return proto.CompactTextString(m) } -func (*DecimalPlaces) ProtoMessage() {} -func (*DecimalPlaces) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{3} -} -func (m *DecimalPlaces) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DecimalPlaces.Unmarshal(m, b) -} -func (m *DecimalPlaces) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DecimalPlaces.Marshal(b, m, deterministic) -} -func (m *DecimalPlaces) XXX_Merge(src proto.Message) { - xxx_messageInfo_DecimalPlaces.Merge(m, src) -} -func (m *DecimalPlaces) XXX_Size() int { - return xxx_messageInfo_DecimalPlaces.Size(m) -} -func (m *DecimalPlaces) XXX_DiscardUnknown() { - xxx_messageInfo_DecimalPlaces.DiscardUnknown(m) -} - -var xxx_messageInfo_DecimalPlaces proto.InternalMessageInfo - -func (m *DecimalPlaces) GetIsEnforced() bool { - if m != nil { - return m.IsEnforced - } - return false -} - -func (m *DecimalPlaces) GetDigits() int32 { - if m != nil { - return m.Digits - } - return 0 -} - -type TableOptions struct { - VerticalTimeAxis bool `protobuf:"varint,2,opt,name=verticalTimeAxis,proto3" json:"verticalTimeAxis,omitempty"` - SortBy *RenamableField `protobuf:"bytes,3,opt,name=sortBy,proto3" json:"sortBy,omitempty"` - Wrapping string `protobuf:"bytes,4,opt,name=wrapping,proto3" json:"wrapping,omitempty"` - FixFirstColumn bool `protobuf:"varint,6,opt,name=fixFirstColumn,proto3" json:"fixFirstColumn,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *TableOptions) Reset() { *m = TableOptions{} } -func (m *TableOptions) String() string { return proto.CompactTextString(m) } -func (*TableOptions) ProtoMessage() {} -func (*TableOptions) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{4} -} -func (m *TableOptions) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_TableOptions.Unmarshal(m, b) -} -func (m *TableOptions) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_TableOptions.Marshal(b, m, deterministic) -} -func (m *TableOptions) XXX_Merge(src proto.Message) { - xxx_messageInfo_TableOptions.Merge(m, src) -} -func (m *TableOptions) XXX_Size() int { - return xxx_messageInfo_TableOptions.Size(m) -} -func (m *TableOptions) XXX_DiscardUnknown() { - xxx_messageInfo_TableOptions.DiscardUnknown(m) -} - -var xxx_messageInfo_TableOptions proto.InternalMessageInfo - -func (m *TableOptions) GetVerticalTimeAxis() bool { - if m != nil { - return m.VerticalTimeAxis - } - return false -} - -func (m *TableOptions) GetSortBy() *RenamableField { - if m != nil { - return m.SortBy - } - return nil -} - -func (m *TableOptions) GetWrapping() string { - if m != nil { - return m.Wrapping - } - return "" -} - -func (m *TableOptions) GetFixFirstColumn() bool { - if m != nil { - return m.FixFirstColumn - } - return false -} - -type RenamableField struct { - InternalName string `protobuf:"bytes,1,opt,name=internalName,proto3" json:"internalName,omitempty"` - DisplayName string `protobuf:"bytes,2,opt,name=displayName,proto3" json:"displayName,omitempty"` - Visible bool `protobuf:"varint,3,opt,name=visible,proto3" json:"visible,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *RenamableField) Reset() { *m = RenamableField{} } -func (m *RenamableField) String() string { return proto.CompactTextString(m) } -func (*RenamableField) ProtoMessage() {} -func (*RenamableField) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{5} -} -func (m *RenamableField) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_RenamableField.Unmarshal(m, b) -} -func (m *RenamableField) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_RenamableField.Marshal(b, m, deterministic) -} -func (m *RenamableField) XXX_Merge(src proto.Message) { - xxx_messageInfo_RenamableField.Merge(m, src) -} -func (m *RenamableField) XXX_Size() int { - return xxx_messageInfo_RenamableField.Size(m) -} -func (m *RenamableField) XXX_DiscardUnknown() { - xxx_messageInfo_RenamableField.DiscardUnknown(m) -} - -var xxx_messageInfo_RenamableField proto.InternalMessageInfo - -func (m *RenamableField) GetInternalName() string { - if m != nil { - return m.InternalName - } - return "" -} - -func (m *RenamableField) GetDisplayName() string { - if m != nil { - return m.DisplayName - } - return "" -} - -func (m *RenamableField) GetVisible() bool { - if m != nil { - return m.Visible - } - return false -} - -type Color struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - Type string `protobuf:"bytes,2,opt,name=Type,proto3" json:"Type,omitempty"` - Hex string `protobuf:"bytes,3,opt,name=Hex,proto3" json:"Hex,omitempty"` - Name string `protobuf:"bytes,4,opt,name=Name,proto3" json:"Name,omitempty"` - Value string `protobuf:"bytes,5,opt,name=Value,proto3" json:"Value,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Color) Reset() { *m = Color{} } -func (m *Color) String() string { return proto.CompactTextString(m) } -func (*Color) ProtoMessage() {} -func (*Color) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{6} -} -func (m *Color) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Color.Unmarshal(m, b) -} -func (m *Color) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Color.Marshal(b, m, deterministic) -} -func (m *Color) XXX_Merge(src proto.Message) { - xxx_messageInfo_Color.Merge(m, src) -} -func (m *Color) XXX_Size() int { - return xxx_messageInfo_Color.Size(m) -} -func (m *Color) XXX_DiscardUnknown() { - xxx_messageInfo_Color.DiscardUnknown(m) -} - -var xxx_messageInfo_Color proto.InternalMessageInfo - -func (m *Color) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Color) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Color) GetHex() string { - if m != nil { - return m.Hex - } - return "" -} - -func (m *Color) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Color) GetValue() string { - if m != nil { - return m.Value - } - return "" -} - -type Legend struct { - Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"` - Orientation string `protobuf:"bytes,2,opt,name=Orientation,proto3" json:"Orientation,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Legend) Reset() { *m = Legend{} } -func (m *Legend) String() string { return proto.CompactTextString(m) } -func (*Legend) ProtoMessage() {} -func (*Legend) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{7} -} -func (m *Legend) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Legend.Unmarshal(m, b) -} -func (m *Legend) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Legend.Marshal(b, m, deterministic) -} -func (m *Legend) XXX_Merge(src proto.Message) { - xxx_messageInfo_Legend.Merge(m, src) -} -func (m *Legend) XXX_Size() int { - return xxx_messageInfo_Legend.Size(m) -} -func (m *Legend) XXX_DiscardUnknown() { - xxx_messageInfo_Legend.DiscardUnknown(m) -} - -var xxx_messageInfo_Legend proto.InternalMessageInfo - -func (m *Legend) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Legend) GetOrientation() string { - if m != nil { - return m.Orientation - } - return "" -} - -type Axis struct { - LegacyBounds []int64 `protobuf:"varint,1,rep,packed,name=legacyBounds,proto3" json:"legacyBounds,omitempty"` - Bounds []string `protobuf:"bytes,2,rep,name=bounds,proto3" json:"bounds,omitempty"` - Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"` - Prefix string `protobuf:"bytes,4,opt,name=prefix,proto3" json:"prefix,omitempty"` - Suffix string `protobuf:"bytes,5,opt,name=suffix,proto3" json:"suffix,omitempty"` - Base string `protobuf:"bytes,6,opt,name=base,proto3" json:"base,omitempty"` - Scale string `protobuf:"bytes,7,opt,name=scale,proto3" json:"scale,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Axis) Reset() { *m = Axis{} } -func (m *Axis) String() string { return proto.CompactTextString(m) } -func (*Axis) ProtoMessage() {} -func (*Axis) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{8} -} -func (m *Axis) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Axis.Unmarshal(m, b) -} -func (m *Axis) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Axis.Marshal(b, m, deterministic) -} -func (m *Axis) XXX_Merge(src proto.Message) { - xxx_messageInfo_Axis.Merge(m, src) -} -func (m *Axis) XXX_Size() int { - return xxx_messageInfo_Axis.Size(m) -} -func (m *Axis) XXX_DiscardUnknown() { - xxx_messageInfo_Axis.DiscardUnknown(m) -} - -var xxx_messageInfo_Axis proto.InternalMessageInfo - -func (m *Axis) GetLegacyBounds() []int64 { - if m != nil { - return m.LegacyBounds - } - return nil -} - -func (m *Axis) GetBounds() []string { - if m != nil { - return m.Bounds - } - return nil -} - -func (m *Axis) GetLabel() string { - if m != nil { - return m.Label - } - return "" -} - -func (m *Axis) GetPrefix() string { - if m != nil { - return m.Prefix - } - return "" -} - -func (m *Axis) GetSuffix() string { - if m != nil { - return m.Suffix - } - return "" -} - -func (m *Axis) GetBase() string { - if m != nil { - return m.Base - } - return "" -} - -func (m *Axis) GetScale() string { - if m != nil { - return m.Scale - } - return "" -} - -type Template struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - TempVar string `protobuf:"bytes,2,opt,name=temp_var,json=tempVar,proto3" json:"temp_var,omitempty"` - Values []*TemplateValue `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty"` - Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"` - Label string `protobuf:"bytes,5,opt,name=label,proto3" json:"label,omitempty"` - Query *TemplateQuery `protobuf:"bytes,6,opt,name=query,proto3" json:"query,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Template) Reset() { *m = Template{} } -func (m *Template) String() string { return proto.CompactTextString(m) } -func (*Template) ProtoMessage() {} -func (*Template) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{9} -} -func (m *Template) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Template.Unmarshal(m, b) -} -func (m *Template) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Template.Marshal(b, m, deterministic) -} -func (m *Template) XXX_Merge(src proto.Message) { - xxx_messageInfo_Template.Merge(m, src) -} -func (m *Template) XXX_Size() int { - return xxx_messageInfo_Template.Size(m) -} -func (m *Template) XXX_DiscardUnknown() { - xxx_messageInfo_Template.DiscardUnknown(m) -} - -var xxx_messageInfo_Template proto.InternalMessageInfo - -func (m *Template) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Template) GetTempVar() string { - if m != nil { - return m.TempVar - } - return "" -} - -func (m *Template) GetValues() []*TemplateValue { - if m != nil { - return m.Values - } - return nil -} - -func (m *Template) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Template) GetLabel() string { - if m != nil { - return m.Label - } - return "" -} - -func (m *Template) GetQuery() *TemplateQuery { - if m != nil { - return m.Query - } - return nil -} - -type TemplateValue struct { - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` - Selected bool `protobuf:"varint,3,opt,name=selected,proto3" json:"selected,omitempty"` - Key string `protobuf:"bytes,4,opt,name=key,proto3" json:"key,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *TemplateValue) Reset() { *m = TemplateValue{} } -func (m *TemplateValue) String() string { return proto.CompactTextString(m) } -func (*TemplateValue) ProtoMessage() {} -func (*TemplateValue) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{10} -} -func (m *TemplateValue) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_TemplateValue.Unmarshal(m, b) -} -func (m *TemplateValue) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_TemplateValue.Marshal(b, m, deterministic) -} -func (m *TemplateValue) XXX_Merge(src proto.Message) { - xxx_messageInfo_TemplateValue.Merge(m, src) -} -func (m *TemplateValue) XXX_Size() int { - return xxx_messageInfo_TemplateValue.Size(m) -} -func (m *TemplateValue) XXX_DiscardUnknown() { - xxx_messageInfo_TemplateValue.DiscardUnknown(m) -} - -var xxx_messageInfo_TemplateValue proto.InternalMessageInfo - -func (m *TemplateValue) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *TemplateValue) GetValue() string { - if m != nil { - return m.Value - } - return "" -} - -func (m *TemplateValue) GetSelected() bool { - if m != nil { - return m.Selected - } - return false -} - -func (m *TemplateValue) GetKey() string { - if m != nil { - return m.Key - } - return "" -} - -type TemplateQuery struct { - Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` - Db string `protobuf:"bytes,2,opt,name=db,proto3" json:"db,omitempty"` - Rp string `protobuf:"bytes,3,opt,name=rp,proto3" json:"rp,omitempty"` - Measurement string `protobuf:"bytes,4,opt,name=measurement,proto3" json:"measurement,omitempty"` - TagKey string `protobuf:"bytes,5,opt,name=tag_key,json=tagKey,proto3" json:"tag_key,omitempty"` - FieldKey string `protobuf:"bytes,6,opt,name=field_key,json=fieldKey,proto3" json:"field_key,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *TemplateQuery) Reset() { *m = TemplateQuery{} } -func (m *TemplateQuery) String() string { return proto.CompactTextString(m) } -func (*TemplateQuery) ProtoMessage() {} -func (*TemplateQuery) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{11} -} -func (m *TemplateQuery) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_TemplateQuery.Unmarshal(m, b) -} -func (m *TemplateQuery) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_TemplateQuery.Marshal(b, m, deterministic) -} -func (m *TemplateQuery) XXX_Merge(src proto.Message) { - xxx_messageInfo_TemplateQuery.Merge(m, src) -} -func (m *TemplateQuery) XXX_Size() int { - return xxx_messageInfo_TemplateQuery.Size(m) -} -func (m *TemplateQuery) XXX_DiscardUnknown() { - xxx_messageInfo_TemplateQuery.DiscardUnknown(m) -} - -var xxx_messageInfo_TemplateQuery proto.InternalMessageInfo - -func (m *TemplateQuery) GetCommand() string { - if m != nil { - return m.Command - } - return "" -} - -func (m *TemplateQuery) GetDb() string { - if m != nil { - return m.Db - } - return "" -} - -func (m *TemplateQuery) GetRp() string { - if m != nil { - return m.Rp - } - return "" -} - -func (m *TemplateQuery) GetMeasurement() string { - if m != nil { - return m.Measurement - } - return "" -} - -func (m *TemplateQuery) GetTagKey() string { - if m != nil { - return m.TagKey - } - return "" -} - -func (m *TemplateQuery) GetFieldKey() string { - if m != nil { - return m.FieldKey - } - return "" -} - -type Server struct { - ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Username string `protobuf:"bytes,3,opt,name=Username,proto3" json:"Username,omitempty"` - Password string `protobuf:"bytes,4,opt,name=Password,proto3" json:"Password,omitempty"` - URL string `protobuf:"bytes,5,opt,name=URL,proto3" json:"URL,omitempty"` - SrcID int64 `protobuf:"varint,6,opt,name=SrcID,proto3" json:"SrcID,omitempty"` - Active bool `protobuf:"varint,7,opt,name=Active,proto3" json:"Active,omitempty"` - Organization string `protobuf:"bytes,8,opt,name=Organization,proto3" json:"Organization,omitempty"` - InsecureSkipVerify bool `protobuf:"varint,9,opt,name=InsecureSkipVerify,proto3" json:"InsecureSkipVerify,omitempty"` - Type string `protobuf:"bytes,10,opt,name=Type,proto3" json:"Type,omitempty"` - MetadataJSON string `protobuf:"bytes,11,opt,name=MetadataJSON,proto3" json:"MetadataJSON,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Server) Reset() { *m = Server{} } -func (m *Server) String() string { return proto.CompactTextString(m) } -func (*Server) ProtoMessage() {} -func (*Server) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{12} -} -func (m *Server) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Server.Unmarshal(m, b) -} -func (m *Server) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Server.Marshal(b, m, deterministic) -} -func (m *Server) XXX_Merge(src proto.Message) { - xxx_messageInfo_Server.Merge(m, src) -} -func (m *Server) XXX_Size() int { - return xxx_messageInfo_Server.Size(m) -} -func (m *Server) XXX_DiscardUnknown() { - xxx_messageInfo_Server.DiscardUnknown(m) -} - -var xxx_messageInfo_Server proto.InternalMessageInfo - -func (m *Server) GetID() int64 { - if m != nil { - return m.ID - } - return 0 -} - -func (m *Server) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Server) GetUsername() string { - if m != nil { - return m.Username - } - return "" -} - -func (m *Server) GetPassword() string { - if m != nil { - return m.Password - } - return "" -} - -func (m *Server) GetURL() string { - if m != nil { - return m.URL - } - return "" -} - -func (m *Server) GetSrcID() int64 { - if m != nil { - return m.SrcID - } - return 0 -} - -func (m *Server) GetActive() bool { - if m != nil { - return m.Active - } - return false -} - -func (m *Server) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -func (m *Server) GetInsecureSkipVerify() bool { - if m != nil { - return m.InsecureSkipVerify - } - return false -} - -func (m *Server) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Server) GetMetadataJSON() string { - if m != nil { - return m.MetadataJSON - } - return "" -} - -type Layout struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - Application string `protobuf:"bytes,2,opt,name=Application,proto3" json:"Application,omitempty"` - Measurement string `protobuf:"bytes,3,opt,name=Measurement,proto3" json:"Measurement,omitempty"` - Cells []*Cell `protobuf:"bytes,4,rep,name=Cells,proto3" json:"Cells,omitempty"` - Autoflow bool `protobuf:"varint,5,opt,name=Autoflow,proto3" json:"Autoflow,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Layout) Reset() { *m = Layout{} } -func (m *Layout) String() string { return proto.CompactTextString(m) } -func (*Layout) ProtoMessage() {} -func (*Layout) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{13} -} -func (m *Layout) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Layout.Unmarshal(m, b) -} -func (m *Layout) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Layout.Marshal(b, m, deterministic) -} -func (m *Layout) XXX_Merge(src proto.Message) { - xxx_messageInfo_Layout.Merge(m, src) -} -func (m *Layout) XXX_Size() int { - return xxx_messageInfo_Layout.Size(m) -} -func (m *Layout) XXX_DiscardUnknown() { - xxx_messageInfo_Layout.DiscardUnknown(m) -} - -var xxx_messageInfo_Layout proto.InternalMessageInfo - -func (m *Layout) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Layout) GetApplication() string { - if m != nil { - return m.Application - } - return "" -} - -func (m *Layout) GetMeasurement() string { - if m != nil { - return m.Measurement - } - return "" -} - -func (m *Layout) GetCells() []*Cell { - if m != nil { - return m.Cells - } - return nil -} - -func (m *Layout) GetAutoflow() bool { - if m != nil { - return m.Autoflow - } - return false -} - -type Cell struct { - X int32 `protobuf:"varint,1,opt,name=x,proto3" json:"x,omitempty"` - Y int32 `protobuf:"varint,2,opt,name=y,proto3" json:"y,omitempty"` - W int32 `protobuf:"varint,3,opt,name=w,proto3" json:"w,omitempty"` - H int32 `protobuf:"varint,4,opt,name=h,proto3" json:"h,omitempty"` - Queries []*Query `protobuf:"bytes,5,rep,name=queries,proto3" json:"queries,omitempty"` - I string `protobuf:"bytes,6,opt,name=i,proto3" json:"i,omitempty"` - Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"` - Yranges []int64 `protobuf:"varint,8,rep,packed,name=yranges,proto3" json:"yranges,omitempty"` - Ylabels []string `protobuf:"bytes,9,rep,name=ylabels,proto3" json:"ylabels,omitempty"` - Type string `protobuf:"bytes,10,opt,name=type,proto3" json:"type,omitempty"` - Axes map[string]*Axis `protobuf:"bytes,11,rep,name=axes,proto3" json:"axes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Cell) Reset() { *m = Cell{} } -func (m *Cell) String() string { return proto.CompactTextString(m) } -func (*Cell) ProtoMessage() {} -func (*Cell) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{14} -} -func (m *Cell) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Cell.Unmarshal(m, b) -} -func (m *Cell) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Cell.Marshal(b, m, deterministic) -} -func (m *Cell) XXX_Merge(src proto.Message) { - xxx_messageInfo_Cell.Merge(m, src) -} -func (m *Cell) XXX_Size() int { - return xxx_messageInfo_Cell.Size(m) -} -func (m *Cell) XXX_DiscardUnknown() { - xxx_messageInfo_Cell.DiscardUnknown(m) -} - -var xxx_messageInfo_Cell proto.InternalMessageInfo - -func (m *Cell) GetX() int32 { - if m != nil { - return m.X - } - return 0 -} - -func (m *Cell) GetY() int32 { - if m != nil { - return m.Y - } - return 0 -} - -func (m *Cell) GetW() int32 { - if m != nil { - return m.W - } - return 0 -} - -func (m *Cell) GetH() int32 { - if m != nil { - return m.H - } - return 0 -} - -func (m *Cell) GetQueries() []*Query { - if m != nil { - return m.Queries - } - return nil -} - -func (m *Cell) GetI() string { - if m != nil { - return m.I - } - return "" -} - -func (m *Cell) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Cell) GetYranges() []int64 { - if m != nil { - return m.Yranges - } - return nil -} - -func (m *Cell) GetYlabels() []string { - if m != nil { - return m.Ylabels - } - return nil -} - -func (m *Cell) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *Cell) GetAxes() map[string]*Axis { - if m != nil { - return m.Axes - } - return nil -} - -type Query struct { - Command string `protobuf:"bytes,1,opt,name=Command,proto3" json:"Command,omitempty"` - DB string `protobuf:"bytes,2,opt,name=DB,proto3" json:"DB,omitempty"` - RP string `protobuf:"bytes,3,opt,name=RP,proto3" json:"RP,omitempty"` - GroupBys []string `protobuf:"bytes,4,rep,name=GroupBys,proto3" json:"GroupBys,omitempty"` - Wheres []string `protobuf:"bytes,5,rep,name=Wheres,proto3" json:"Wheres,omitempty"` - Label string `protobuf:"bytes,6,opt,name=Label,proto3" json:"Label,omitempty"` - Range *Range `protobuf:"bytes,7,opt,name=Range,proto3" json:"Range,omitempty"` - Source string `protobuf:"bytes,8,opt,name=Source,proto3" json:"Source,omitempty"` - Shifts []*TimeShift `protobuf:"bytes,9,rep,name=Shifts,proto3" json:"Shifts,omitempty"` - Type string `protobuf:"bytes,10,opt,name=Type,proto3" json:"Type,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Query) Reset() { *m = Query{} } -func (m *Query) String() string { return proto.CompactTextString(m) } -func (*Query) ProtoMessage() {} -func (*Query) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{15} -} -func (m *Query) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Query.Unmarshal(m, b) -} -func (m *Query) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Query.Marshal(b, m, deterministic) -} -func (m *Query) XXX_Merge(src proto.Message) { - xxx_messageInfo_Query.Merge(m, src) -} -func (m *Query) XXX_Size() int { - return xxx_messageInfo_Query.Size(m) -} -func (m *Query) XXX_DiscardUnknown() { - xxx_messageInfo_Query.DiscardUnknown(m) -} - -var xxx_messageInfo_Query proto.InternalMessageInfo - -func (m *Query) GetCommand() string { - if m != nil { - return m.Command - } - return "" -} - -func (m *Query) GetDB() string { - if m != nil { - return m.DB - } - return "" -} - -func (m *Query) GetRP() string { - if m != nil { - return m.RP - } - return "" -} - -func (m *Query) GetGroupBys() []string { - if m != nil { - return m.GroupBys - } - return nil -} - -func (m *Query) GetWheres() []string { - if m != nil { - return m.Wheres - } - return nil -} - -func (m *Query) GetLabel() string { - if m != nil { - return m.Label - } - return "" -} - -func (m *Query) GetRange() *Range { - if m != nil { - return m.Range - } - return nil -} - -func (m *Query) GetSource() string { - if m != nil { - return m.Source - } - return "" -} - -func (m *Query) GetShifts() []*TimeShift { - if m != nil { - return m.Shifts - } - return nil -} - -func (m *Query) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -type TimeShift struct { - Label string `protobuf:"bytes,1,opt,name=Label,proto3" json:"Label,omitempty"` - Unit string `protobuf:"bytes,2,opt,name=Unit,proto3" json:"Unit,omitempty"` - Quantity string `protobuf:"bytes,3,opt,name=Quantity,proto3" json:"Quantity,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *TimeShift) Reset() { *m = TimeShift{} } -func (m *TimeShift) String() string { return proto.CompactTextString(m) } -func (*TimeShift) ProtoMessage() {} -func (*TimeShift) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{16} -} -func (m *TimeShift) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_TimeShift.Unmarshal(m, b) -} -func (m *TimeShift) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_TimeShift.Marshal(b, m, deterministic) -} -func (m *TimeShift) XXX_Merge(src proto.Message) { - xxx_messageInfo_TimeShift.Merge(m, src) -} -func (m *TimeShift) XXX_Size() int { - return xxx_messageInfo_TimeShift.Size(m) -} -func (m *TimeShift) XXX_DiscardUnknown() { - xxx_messageInfo_TimeShift.DiscardUnknown(m) -} - -var xxx_messageInfo_TimeShift proto.InternalMessageInfo - -func (m *TimeShift) GetLabel() string { - if m != nil { - return m.Label - } - return "" -} - -func (m *TimeShift) GetUnit() string { - if m != nil { - return m.Unit - } - return "" -} - -func (m *TimeShift) GetQuantity() string { - if m != nil { - return m.Quantity - } - return "" -} - -type Range struct { - Upper int64 `protobuf:"varint,1,opt,name=Upper,proto3" json:"Upper,omitempty"` - Lower int64 `protobuf:"varint,2,opt,name=Lower,proto3" json:"Lower,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Range) Reset() { *m = Range{} } -func (m *Range) String() string { return proto.CompactTextString(m) } -func (*Range) ProtoMessage() {} -func (*Range) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{17} -} -func (m *Range) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Range.Unmarshal(m, b) -} -func (m *Range) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Range.Marshal(b, m, deterministic) -} -func (m *Range) XXX_Merge(src proto.Message) { - xxx_messageInfo_Range.Merge(m, src) -} -func (m *Range) XXX_Size() int { - return xxx_messageInfo_Range.Size(m) -} -func (m *Range) XXX_DiscardUnknown() { - xxx_messageInfo_Range.DiscardUnknown(m) -} - -var xxx_messageInfo_Range proto.InternalMessageInfo - -func (m *Range) GetUpper() int64 { - if m != nil { - return m.Upper - } - return 0 -} - -func (m *Range) GetLower() int64 { - if m != nil { - return m.Lower - } - return 0 -} - -type AlertRule struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - JSON string `protobuf:"bytes,2,opt,name=JSON,proto3" json:"JSON,omitempty"` - SrcID int64 `protobuf:"varint,3,opt,name=SrcID,proto3" json:"SrcID,omitempty"` - KapaID int64 `protobuf:"varint,4,opt,name=KapaID,proto3" json:"KapaID,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *AlertRule) Reset() { *m = AlertRule{} } -func (m *AlertRule) String() string { return proto.CompactTextString(m) } -func (*AlertRule) ProtoMessage() {} -func (*AlertRule) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{18} -} -func (m *AlertRule) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_AlertRule.Unmarshal(m, b) -} -func (m *AlertRule) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_AlertRule.Marshal(b, m, deterministic) -} -func (m *AlertRule) XXX_Merge(src proto.Message) { - xxx_messageInfo_AlertRule.Merge(m, src) -} -func (m *AlertRule) XXX_Size() int { - return xxx_messageInfo_AlertRule.Size(m) -} -func (m *AlertRule) XXX_DiscardUnknown() { - xxx_messageInfo_AlertRule.DiscardUnknown(m) -} - -var xxx_messageInfo_AlertRule proto.InternalMessageInfo - -func (m *AlertRule) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *AlertRule) GetJSON() string { - if m != nil { - return m.JSON - } - return "" -} - -func (m *AlertRule) GetSrcID() int64 { - if m != nil { - return m.SrcID - } - return 0 -} - -func (m *AlertRule) GetKapaID() int64 { - if m != nil { - return m.KapaID - } - return 0 -} - -type User struct { - ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Provider string `protobuf:"bytes,3,opt,name=Provider,proto3" json:"Provider,omitempty"` - Scheme string `protobuf:"bytes,4,opt,name=Scheme,proto3" json:"Scheme,omitempty"` - Roles []*Role `protobuf:"bytes,5,rep,name=Roles,proto3" json:"Roles,omitempty"` - SuperAdmin bool `protobuf:"varint,6,opt,name=SuperAdmin,proto3" json:"SuperAdmin,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *User) Reset() { *m = User{} } -func (m *User) String() string { return proto.CompactTextString(m) } -func (*User) ProtoMessage() {} -func (*User) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{19} -} -func (m *User) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_User.Unmarshal(m, b) -} -func (m *User) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_User.Marshal(b, m, deterministic) -} -func (m *User) XXX_Merge(src proto.Message) { - xxx_messageInfo_User.Merge(m, src) -} -func (m *User) XXX_Size() int { - return xxx_messageInfo_User.Size(m) -} -func (m *User) XXX_DiscardUnknown() { - xxx_messageInfo_User.DiscardUnknown(m) -} - -var xxx_messageInfo_User proto.InternalMessageInfo - -func (m *User) GetID() uint64 { - if m != nil { - return m.ID - } - return 0 -} - -func (m *User) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *User) GetProvider() string { - if m != nil { - return m.Provider - } - return "" -} - -func (m *User) GetScheme() string { - if m != nil { - return m.Scheme - } - return "" -} - -func (m *User) GetRoles() []*Role { - if m != nil { - return m.Roles - } - return nil -} - -func (m *User) GetSuperAdmin() bool { - if m != nil { - return m.SuperAdmin - } - return false -} - -type Role struct { - Organization string `protobuf:"bytes,1,opt,name=Organization,proto3" json:"Organization,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Role) Reset() { *m = Role{} } -func (m *Role) String() string { return proto.CompactTextString(m) } -func (*Role) ProtoMessage() {} -func (*Role) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{20} -} -func (m *Role) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Role.Unmarshal(m, b) -} -func (m *Role) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Role.Marshal(b, m, deterministic) -} -func (m *Role) XXX_Merge(src proto.Message) { - xxx_messageInfo_Role.Merge(m, src) -} -func (m *Role) XXX_Size() int { - return xxx_messageInfo_Role.Size(m) -} -func (m *Role) XXX_DiscardUnknown() { - xxx_messageInfo_Role.DiscardUnknown(m) -} - -var xxx_messageInfo_Role proto.InternalMessageInfo - -func (m *Role) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -func (m *Role) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -type Mapping struct { - Provider string `protobuf:"bytes,1,opt,name=Provider,proto3" json:"Provider,omitempty"` - Scheme string `protobuf:"bytes,2,opt,name=Scheme,proto3" json:"Scheme,omitempty"` - ProviderOrganization string `protobuf:"bytes,3,opt,name=ProviderOrganization,proto3" json:"ProviderOrganization,omitempty"` - ID string `protobuf:"bytes,4,opt,name=ID,proto3" json:"ID,omitempty"` - Organization string `protobuf:"bytes,5,opt,name=Organization,proto3" json:"Organization,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Mapping) Reset() { *m = Mapping{} } -func (m *Mapping) String() string { return proto.CompactTextString(m) } -func (*Mapping) ProtoMessage() {} -func (*Mapping) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{21} -} -func (m *Mapping) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Mapping.Unmarshal(m, b) -} -func (m *Mapping) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Mapping.Marshal(b, m, deterministic) -} -func (m *Mapping) XXX_Merge(src proto.Message) { - xxx_messageInfo_Mapping.Merge(m, src) -} -func (m *Mapping) XXX_Size() int { - return xxx_messageInfo_Mapping.Size(m) -} -func (m *Mapping) XXX_DiscardUnknown() { - xxx_messageInfo_Mapping.DiscardUnknown(m) -} - -var xxx_messageInfo_Mapping proto.InternalMessageInfo - -func (m *Mapping) GetProvider() string { - if m != nil { - return m.Provider - } - return "" -} - -func (m *Mapping) GetScheme() string { - if m != nil { - return m.Scheme - } - return "" -} - -func (m *Mapping) GetProviderOrganization() string { - if m != nil { - return m.ProviderOrganization - } - return "" -} - -func (m *Mapping) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Mapping) GetOrganization() string { - if m != nil { - return m.Organization - } - return "" -} - -type Organization struct { - ID string `protobuf:"bytes,1,opt,name=ID,proto3" json:"ID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - DefaultRole string `protobuf:"bytes,3,opt,name=DefaultRole,proto3" json:"DefaultRole,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Organization) Reset() { *m = Organization{} } -func (m *Organization) String() string { return proto.CompactTextString(m) } -func (*Organization) ProtoMessage() {} -func (*Organization) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{22} -} -func (m *Organization) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Organization.Unmarshal(m, b) -} -func (m *Organization) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Organization.Marshal(b, m, deterministic) -} -func (m *Organization) XXX_Merge(src proto.Message) { - xxx_messageInfo_Organization.Merge(m, src) -} -func (m *Organization) XXX_Size() int { - return xxx_messageInfo_Organization.Size(m) -} -func (m *Organization) XXX_DiscardUnknown() { - xxx_messageInfo_Organization.DiscardUnknown(m) -} - -var xxx_messageInfo_Organization proto.InternalMessageInfo - -func (m *Organization) GetID() string { - if m != nil { - return m.ID - } - return "" -} - -func (m *Organization) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *Organization) GetDefaultRole() string { - if m != nil { - return m.DefaultRole - } - return "" -} - -type Config struct { - Auth *AuthConfig `protobuf:"bytes,1,opt,name=Auth,proto3" json:"Auth,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *Config) Reset() { *m = Config{} } -func (m *Config) String() string { return proto.CompactTextString(m) } -func (*Config) ProtoMessage() {} -func (*Config) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{23} -} -func (m *Config) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Config.Unmarshal(m, b) -} -func (m *Config) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Config.Marshal(b, m, deterministic) -} -func (m *Config) XXX_Merge(src proto.Message) { - xxx_messageInfo_Config.Merge(m, src) -} -func (m *Config) XXX_Size() int { - return xxx_messageInfo_Config.Size(m) -} -func (m *Config) XXX_DiscardUnknown() { - xxx_messageInfo_Config.DiscardUnknown(m) -} - -var xxx_messageInfo_Config proto.InternalMessageInfo - -func (m *Config) GetAuth() *AuthConfig { - if m != nil { - return m.Auth - } - return nil -} - -type AuthConfig struct { - SuperAdminNewUsers bool `protobuf:"varint,1,opt,name=SuperAdminNewUsers,proto3" json:"SuperAdminNewUsers,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *AuthConfig) Reset() { *m = AuthConfig{} } -func (m *AuthConfig) String() string { return proto.CompactTextString(m) } -func (*AuthConfig) ProtoMessage() {} -func (*AuthConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{24} -} -func (m *AuthConfig) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_AuthConfig.Unmarshal(m, b) -} -func (m *AuthConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_AuthConfig.Marshal(b, m, deterministic) -} -func (m *AuthConfig) XXX_Merge(src proto.Message) { - xxx_messageInfo_AuthConfig.Merge(m, src) -} -func (m *AuthConfig) XXX_Size() int { - return xxx_messageInfo_AuthConfig.Size(m) -} -func (m *AuthConfig) XXX_DiscardUnknown() { - xxx_messageInfo_AuthConfig.DiscardUnknown(m) -} - -var xxx_messageInfo_AuthConfig proto.InternalMessageInfo - -func (m *AuthConfig) GetSuperAdminNewUsers() bool { - if m != nil { - return m.SuperAdminNewUsers - } - return false -} - -type OrganizationConfig struct { - OrganizationID string `protobuf:"bytes,1,opt,name=OrganizationID,proto3" json:"OrganizationID,omitempty"` - LogViewer *LogViewerConfig `protobuf:"bytes,2,opt,name=LogViewer,proto3" json:"LogViewer,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *OrganizationConfig) Reset() { *m = OrganizationConfig{} } -func (m *OrganizationConfig) String() string { return proto.CompactTextString(m) } -func (*OrganizationConfig) ProtoMessage() {} -func (*OrganizationConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{25} -} -func (m *OrganizationConfig) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_OrganizationConfig.Unmarshal(m, b) -} -func (m *OrganizationConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_OrganizationConfig.Marshal(b, m, deterministic) -} -func (m *OrganizationConfig) XXX_Merge(src proto.Message) { - xxx_messageInfo_OrganizationConfig.Merge(m, src) -} -func (m *OrganizationConfig) XXX_Size() int { - return xxx_messageInfo_OrganizationConfig.Size(m) -} -func (m *OrganizationConfig) XXX_DiscardUnknown() { - xxx_messageInfo_OrganizationConfig.DiscardUnknown(m) -} - -var xxx_messageInfo_OrganizationConfig proto.InternalMessageInfo - -func (m *OrganizationConfig) GetOrganizationID() string { - if m != nil { - return m.OrganizationID - } - return "" -} - -func (m *OrganizationConfig) GetLogViewer() *LogViewerConfig { - if m != nil { - return m.LogViewer - } - return nil -} - -type LogViewerConfig struct { - Columns []*LogViewerColumn `protobuf:"bytes,1,rep,name=Columns,proto3" json:"Columns,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *LogViewerConfig) Reset() { *m = LogViewerConfig{} } -func (m *LogViewerConfig) String() string { return proto.CompactTextString(m) } -func (*LogViewerConfig) ProtoMessage() {} -func (*LogViewerConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{26} -} -func (m *LogViewerConfig) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_LogViewerConfig.Unmarshal(m, b) -} -func (m *LogViewerConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_LogViewerConfig.Marshal(b, m, deterministic) -} -func (m *LogViewerConfig) XXX_Merge(src proto.Message) { - xxx_messageInfo_LogViewerConfig.Merge(m, src) -} -func (m *LogViewerConfig) XXX_Size() int { - return xxx_messageInfo_LogViewerConfig.Size(m) -} -func (m *LogViewerConfig) XXX_DiscardUnknown() { - xxx_messageInfo_LogViewerConfig.DiscardUnknown(m) -} - -var xxx_messageInfo_LogViewerConfig proto.InternalMessageInfo - -func (m *LogViewerConfig) GetColumns() []*LogViewerColumn { - if m != nil { - return m.Columns - } - return nil -} - -type LogViewerColumn struct { - Name string `protobuf:"bytes,1,opt,name=Name,proto3" json:"Name,omitempty"` - Position int32 `protobuf:"varint,2,opt,name=Position,proto3" json:"Position,omitempty"` - Encodings []*ColumnEncoding `protobuf:"bytes,3,rep,name=Encodings,proto3" json:"Encodings,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *LogViewerColumn) Reset() { *m = LogViewerColumn{} } -func (m *LogViewerColumn) String() string { return proto.CompactTextString(m) } -func (*LogViewerColumn) ProtoMessage() {} -func (*LogViewerColumn) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{27} -} -func (m *LogViewerColumn) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_LogViewerColumn.Unmarshal(m, b) -} -func (m *LogViewerColumn) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_LogViewerColumn.Marshal(b, m, deterministic) -} -func (m *LogViewerColumn) XXX_Merge(src proto.Message) { - xxx_messageInfo_LogViewerColumn.Merge(m, src) -} -func (m *LogViewerColumn) XXX_Size() int { - return xxx_messageInfo_LogViewerColumn.Size(m) -} -func (m *LogViewerColumn) XXX_DiscardUnknown() { - xxx_messageInfo_LogViewerColumn.DiscardUnknown(m) -} - -var xxx_messageInfo_LogViewerColumn proto.InternalMessageInfo - -func (m *LogViewerColumn) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -func (m *LogViewerColumn) GetPosition() int32 { - if m != nil { - return m.Position - } - return 0 -} - -func (m *LogViewerColumn) GetEncodings() []*ColumnEncoding { - if m != nil { - return m.Encodings - } - return nil -} - -type ColumnEncoding struct { - Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"` - Value string `protobuf:"bytes,2,opt,name=Value,proto3" json:"Value,omitempty"` - Name string `protobuf:"bytes,3,opt,name=Name,proto3" json:"Name,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *ColumnEncoding) Reset() { *m = ColumnEncoding{} } -func (m *ColumnEncoding) String() string { return proto.CompactTextString(m) } -func (*ColumnEncoding) ProtoMessage() {} -func (*ColumnEncoding) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{28} -} -func (m *ColumnEncoding) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ColumnEncoding.Unmarshal(m, b) -} -func (m *ColumnEncoding) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ColumnEncoding.Marshal(b, m, deterministic) -} -func (m *ColumnEncoding) XXX_Merge(src proto.Message) { - xxx_messageInfo_ColumnEncoding.Merge(m, src) -} -func (m *ColumnEncoding) XXX_Size() int { - return xxx_messageInfo_ColumnEncoding.Size(m) -} -func (m *ColumnEncoding) XXX_DiscardUnknown() { - xxx_messageInfo_ColumnEncoding.DiscardUnknown(m) -} - -var xxx_messageInfo_ColumnEncoding proto.InternalMessageInfo - -func (m *ColumnEncoding) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *ColumnEncoding) GetValue() string { - if m != nil { - return m.Value - } - return "" -} - -func (m *ColumnEncoding) GetName() string { - if m != nil { - return m.Name - } - return "" -} - -type BuildInfo struct { - Version string `protobuf:"bytes,1,opt,name=Version,proto3" json:"Version,omitempty"` - Commit string `protobuf:"bytes,2,opt,name=Commit,proto3" json:"Commit,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *BuildInfo) Reset() { *m = BuildInfo{} } -func (m *BuildInfo) String() string { return proto.CompactTextString(m) } -func (*BuildInfo) ProtoMessage() {} -func (*BuildInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_41f4a519b878ee3b, []int{29} -} -func (m *BuildInfo) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_BuildInfo.Unmarshal(m, b) -} -func (m *BuildInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_BuildInfo.Marshal(b, m, deterministic) -} -func (m *BuildInfo) XXX_Merge(src proto.Message) { - xxx_messageInfo_BuildInfo.Merge(m, src) -} -func (m *BuildInfo) XXX_Size() int { - return xxx_messageInfo_BuildInfo.Size(m) -} -func (m *BuildInfo) XXX_DiscardUnknown() { - xxx_messageInfo_BuildInfo.DiscardUnknown(m) -} - -var xxx_messageInfo_BuildInfo proto.InternalMessageInfo - -func (m *BuildInfo) GetVersion() string { - if m != nil { - return m.Version - } - return "" -} - -func (m *BuildInfo) GetCommit() string { - if m != nil { - return m.Commit - } - return "" -} - -func init() { - proto.RegisterType((*Source)(nil), "internal.Source") - proto.RegisterType((*Dashboard)(nil), "internal.Dashboard") - proto.RegisterType((*DashboardCell)(nil), "internal.DashboardCell") - proto.RegisterMapType((map[string]*Axis)(nil), "internal.DashboardCell.AxesEntry") - proto.RegisterType((*DecimalPlaces)(nil), "internal.DecimalPlaces") - proto.RegisterType((*TableOptions)(nil), "internal.TableOptions") - proto.RegisterType((*RenamableField)(nil), "internal.RenamableField") - proto.RegisterType((*Color)(nil), "internal.Color") - proto.RegisterType((*Legend)(nil), "internal.Legend") - proto.RegisterType((*Axis)(nil), "internal.Axis") - proto.RegisterType((*Template)(nil), "internal.Template") - proto.RegisterType((*TemplateValue)(nil), "internal.TemplateValue") - proto.RegisterType((*TemplateQuery)(nil), "internal.TemplateQuery") - proto.RegisterType((*Server)(nil), "internal.Server") - proto.RegisterType((*Layout)(nil), "internal.Layout") - proto.RegisterType((*Cell)(nil), "internal.Cell") - proto.RegisterMapType((map[string]*Axis)(nil), "internal.Cell.AxesEntry") - proto.RegisterType((*Query)(nil), "internal.Query") - proto.RegisterType((*TimeShift)(nil), "internal.TimeShift") - proto.RegisterType((*Range)(nil), "internal.Range") - proto.RegisterType((*AlertRule)(nil), "internal.AlertRule") - proto.RegisterType((*User)(nil), "internal.User") - proto.RegisterType((*Role)(nil), "internal.Role") - proto.RegisterType((*Mapping)(nil), "internal.Mapping") - proto.RegisterType((*Organization)(nil), "internal.Organization") - proto.RegisterType((*Config)(nil), "internal.Config") - proto.RegisterType((*AuthConfig)(nil), "internal.AuthConfig") - proto.RegisterType((*OrganizationConfig)(nil), "internal.OrganizationConfig") - proto.RegisterType((*LogViewerConfig)(nil), "internal.LogViewerConfig") - proto.RegisterType((*LogViewerColumn)(nil), "internal.LogViewerColumn") - proto.RegisterType((*ColumnEncoding)(nil), "internal.ColumnEncoding") - proto.RegisterType((*BuildInfo)(nil), "internal.BuildInfo") -} - -func init() { proto.RegisterFile("internal.proto", fileDescriptor_41f4a519b878ee3b) } - -var fileDescriptor_41f4a519b878ee3b = []byte{ - // 1810 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x4b, 0x6f, 0xdc, 0xc8, - 0x11, 0x06, 0x67, 0x86, 0xa3, 0x61, 0x8d, 0x24, 0x0b, 0x1d, 0x63, 0x97, 0xbb, 0x09, 0x82, 0x09, - 0x91, 0x6c, 0x94, 0xc7, 0x3a, 0x0b, 0x19, 0x79, 0x60, 0xb1, 0xbb, 0x80, 0x1e, 0xb6, 0x23, 0x5b, - 0xb6, 0xe5, 0x96, 0xac, 0x9c, 0x82, 0x45, 0x8b, 0xec, 0x99, 0x69, 0x98, 0x43, 0x32, 0x4d, 0x52, - 0x12, 0x73, 0xce, 0x2d, 0xff, 0x21, 0x40, 0x80, 0xe4, 0x1e, 0x04, 0x39, 0x06, 0xc8, 0x3d, 0x3f, - 0x20, 0xbf, 0x27, 0xa8, 0x7e, 0x90, 0x4d, 0x69, 0x6c, 0x38, 0x40, 0xb0, 0xb7, 0xfe, 0xaa, 0x6a, - 0xaa, 0xab, 0xab, 0xab, 0xbe, 0x2e, 0x0e, 0x6c, 0x8b, 0xac, 0xe2, 0x32, 0x63, 0xe9, 0x83, 0x42, - 0xe6, 0x55, 0x4e, 0x26, 0x16, 0x47, 0x7f, 0x18, 0xc2, 0xf8, 0x2c, 0xaf, 0x65, 0xcc, 0xc9, 0x36, - 0x0c, 0x8e, 0x8f, 0x42, 0x6f, 0xe6, 0xed, 0x0e, 0xe9, 0xe0, 0xf8, 0x88, 0x10, 0x18, 0xbd, 0x60, - 0x2b, 0x1e, 0x0e, 0x66, 0xde, 0x6e, 0x40, 0xd5, 0x1a, 0x65, 0xe7, 0x4d, 0xc1, 0xc3, 0xa1, 0x96, - 0xe1, 0x9a, 0x7c, 0x0c, 0x93, 0xd7, 0x25, 0x7a, 0x5b, 0xf1, 0x70, 0xa4, 0xe4, 0x2d, 0x46, 0xdd, - 0x29, 0x2b, 0xcb, 0xeb, 0x5c, 0x26, 0xa1, 0xaf, 0x75, 0x16, 0x93, 0x1d, 0x18, 0xbe, 0xa6, 0x27, - 0xe1, 0x58, 0x89, 0x71, 0x49, 0x42, 0xd8, 0x38, 0xe2, 0x73, 0x56, 0xa7, 0x55, 0xb8, 0x31, 0xf3, - 0x76, 0x27, 0xd4, 0x42, 0xf4, 0x73, 0xce, 0x53, 0xbe, 0x90, 0x6c, 0x1e, 0x4e, 0xb4, 0x1f, 0x8b, - 0xc9, 0x03, 0x20, 0xc7, 0x59, 0xc9, 0xe3, 0x5a, 0xf2, 0xb3, 0x37, 0xa2, 0xb8, 0xe0, 0x52, 0xcc, - 0x9b, 0x30, 0x50, 0x0e, 0xd6, 0x68, 0x70, 0x97, 0xe7, 0xbc, 0x62, 0xb8, 0x37, 0x28, 0x57, 0x16, - 0x92, 0x08, 0x36, 0xcf, 0x96, 0x4c, 0xf2, 0xe4, 0x8c, 0xc7, 0x92, 0x57, 0xe1, 0x54, 0xa9, 0x7b, - 0x32, 0xb4, 0x79, 0x29, 0x17, 0x2c, 0x13, 0xbf, 0x67, 0x95, 0xc8, 0xb3, 0x70, 0x53, 0xdb, 0xb8, - 0x32, 0xcc, 0x12, 0xcd, 0x53, 0x1e, 0x6e, 0xe9, 0x2c, 0xe1, 0x9a, 0x7c, 0x07, 0x02, 0x73, 0x18, - 0x7a, 0x1a, 0x6e, 0x2b, 0x45, 0x27, 0x88, 0xfe, 0xe1, 0x41, 0x70, 0xc4, 0xca, 0xe5, 0x65, 0xce, - 0x64, 0xf2, 0x5e, 0x37, 0xf1, 0x29, 0xf8, 0x31, 0x4f, 0xd3, 0x32, 0x1c, 0xce, 0x86, 0xbb, 0xd3, - 0xbd, 0x0f, 0x1f, 0xb4, 0x57, 0xdc, 0xfa, 0x39, 0xe4, 0x69, 0x4a, 0xb5, 0x15, 0xf9, 0x0c, 0x82, - 0x8a, 0xaf, 0x8a, 0x94, 0x55, 0xbc, 0x0c, 0x47, 0xea, 0x27, 0xa4, 0xfb, 0xc9, 0xb9, 0x51, 0xd1, - 0xce, 0xe8, 0xce, 0x41, 0xfd, 0xbb, 0x07, 0x8d, 0xfe, 0x33, 0x82, 0xad, 0xde, 0x76, 0x64, 0x13, - 0xbc, 0x1b, 0x15, 0xb9, 0x4f, 0xbd, 0x1b, 0x44, 0x8d, 0x8a, 0xda, 0xa7, 0x5e, 0x83, 0xe8, 0x5a, - 0x55, 0x8e, 0x4f, 0xbd, 0x6b, 0x44, 0x4b, 0x55, 0x2f, 0x3e, 0xf5, 0x96, 0xe4, 0x47, 0xb0, 0xf1, - 0xbb, 0x9a, 0x4b, 0xc1, 0xcb, 0xd0, 0x57, 0xd1, 0xdd, 0xeb, 0xa2, 0x7b, 0x55, 0x73, 0xd9, 0x50, - 0xab, 0xc7, 0x6c, 0xa8, 0x5a, 0xd3, 0x85, 0xa3, 0xd6, 0x28, 0xab, 0xb0, 0x2e, 0x37, 0xb4, 0x0c, - 0xd7, 0x26, 0x8b, 0xba, 0x5a, 0x30, 0x8b, 0x3f, 0x87, 0x11, 0xbb, 0xe1, 0x65, 0x18, 0x28, 0xff, - 0xdf, 0x7b, 0x4b, 0xc2, 0x1e, 0xec, 0xdf, 0xf0, 0xf2, 0x51, 0x56, 0xc9, 0x86, 0x2a, 0x73, 0xf2, - 0x43, 0x18, 0xc7, 0x79, 0x9a, 0xcb, 0x32, 0x84, 0xdb, 0x81, 0x1d, 0xa2, 0x9c, 0x1a, 0x35, 0xd9, - 0x85, 0x71, 0xca, 0x17, 0x3c, 0x4b, 0x54, 0xdd, 0x4c, 0xf7, 0x76, 0x3a, 0xc3, 0x13, 0x25, 0xa7, - 0x46, 0x4f, 0x3e, 0x87, 0xcd, 0x8a, 0x5d, 0xa6, 0xfc, 0x65, 0x81, 0x59, 0x2c, 0x55, 0x0d, 0x4d, - 0xf7, 0x3e, 0x70, 0xee, 0xc3, 0xd1, 0xd2, 0x9e, 0x2d, 0xf9, 0x02, 0x36, 0xe7, 0x82, 0xa7, 0x89, - 0xfd, 0xed, 0x96, 0x0a, 0x2a, 0xec, 0x7e, 0x4b, 0x79, 0xc6, 0x56, 0xf8, 0x8b, 0xc7, 0x68, 0x46, - 0x7b, 0xd6, 0xe4, 0xbb, 0x00, 0x95, 0x58, 0xf1, 0xc7, 0xb9, 0x5c, 0xb1, 0xca, 0x94, 0xa1, 0x23, - 0x21, 0x5f, 0xc2, 0x56, 0xc2, 0x63, 0xb1, 0x62, 0xe9, 0x69, 0xca, 0x62, 0x5e, 0x86, 0xf7, 0x54, - 0x68, 0x6e, 0x75, 0xb9, 0x6a, 0xda, 0xb7, 0xfe, 0xf8, 0x09, 0x04, 0x6d, 0xfa, 0xb0, 0xbf, 0xdf, - 0xf0, 0x46, 0x15, 0x43, 0x40, 0x71, 0x49, 0xbe, 0x0f, 0xfe, 0x15, 0x4b, 0x6b, 0x5d, 0xc8, 0xd3, - 0xbd, 0xed, 0xce, 0xeb, 0xfe, 0x8d, 0x28, 0xa9, 0x56, 0x7e, 0x3e, 0xf8, 0x95, 0x17, 0x3d, 0x81, - 0xad, 0xde, 0x46, 0x18, 0xb8, 0x28, 0x1f, 0x65, 0xf3, 0x5c, 0xc6, 0x3c, 0x51, 0x3e, 0x27, 0xd4, - 0x91, 0x90, 0x0f, 0x60, 0x9c, 0x88, 0x85, 0xa8, 0x4a, 0x53, 0x6e, 0x06, 0x45, 0xff, 0xf4, 0x60, - 0xd3, 0xcd, 0x26, 0xf9, 0x31, 0xec, 0x5c, 0x71, 0x59, 0x89, 0x98, 0xa5, 0xe7, 0x62, 0xc5, 0x71, - 0x63, 0xf5, 0x93, 0x09, 0xbd, 0x23, 0x27, 0x9f, 0xc1, 0xb8, 0xcc, 0x65, 0x75, 0xd0, 0xa8, 0xaa, - 0x7d, 0x57, 0x96, 0x8d, 0x1d, 0xf2, 0xd4, 0xb5, 0x64, 0x45, 0x21, 0xb2, 0x85, 0xe5, 0x42, 0x8b, - 0xc9, 0x27, 0xb0, 0x3d, 0x17, 0x37, 0x8f, 0x85, 0x2c, 0xab, 0xc3, 0x3c, 0xad, 0x57, 0x99, 0xaa, - 0xe0, 0x09, 0xbd, 0x25, 0x7d, 0x3a, 0x9a, 0x78, 0x3b, 0x83, 0xa7, 0xa3, 0x89, 0xbf, 0x33, 0x8e, - 0x0a, 0xd8, 0xee, 0xef, 0x84, 0x6d, 0x69, 0x83, 0x50, 0x9c, 0xa0, 0xd3, 0xdb, 0x93, 0x91, 0x19, - 0x4c, 0x13, 0x51, 0x16, 0x29, 0x6b, 0x1c, 0xda, 0x70, 0x45, 0xc8, 0x81, 0x57, 0xa2, 0x14, 0x97, - 0xa9, 0xa6, 0xf2, 0x09, 0xb5, 0x30, 0x5a, 0x80, 0xaf, 0xca, 0xda, 0x21, 0xa1, 0xc0, 0x92, 0x90, - 0xa2, 0xfe, 0x81, 0x43, 0xfd, 0x3b, 0x30, 0xfc, 0x35, 0xbf, 0x31, 0xaf, 0x01, 0x2e, 0x5b, 0xaa, - 0x1a, 0x39, 0x54, 0x75, 0x1f, 0xfc, 0x0b, 0x75, 0xed, 0x9a, 0x42, 0x34, 0x88, 0xbe, 0x82, 0xb1, - 0x6e, 0x8b, 0xd6, 0xb3, 0xe7, 0x78, 0x9e, 0xc1, 0xf4, 0xa5, 0x14, 0x3c, 0xab, 0x34, 0xf9, 0x98, - 0x23, 0x38, 0xa2, 0xe8, 0xef, 0x1e, 0x8c, 0xd4, 0x2d, 0x45, 0xb0, 0x99, 0xf2, 0x05, 0x8b, 0x9b, - 0x83, 0xbc, 0xce, 0x92, 0x32, 0xf4, 0x66, 0xc3, 0xdd, 0x21, 0xed, 0xc9, 0xb0, 0x3c, 0x2e, 0xb5, - 0x76, 0x30, 0x1b, 0xee, 0x06, 0xd4, 0x20, 0x0c, 0x2d, 0x65, 0x97, 0x3c, 0x35, 0x47, 0xd0, 0x00, - 0xad, 0x0b, 0xc9, 0xe7, 0xe2, 0xc6, 0x1c, 0xc3, 0x20, 0x94, 0x97, 0xf5, 0x1c, 0xe5, 0xfa, 0x24, - 0x06, 0xe1, 0x01, 0x2e, 0x59, 0xd9, 0x32, 0x12, 0xae, 0xd1, 0x73, 0x19, 0xb3, 0xd4, 0x52, 0x92, - 0x06, 0xd1, 0xbf, 0x3c, 0x7c, 0xc8, 0x34, 0xc5, 0xde, 0xc9, 0xf0, 0x47, 0x30, 0x41, 0xfa, 0xfd, - 0xfa, 0x8a, 0x49, 0x73, 0xe0, 0x0d, 0xc4, 0x17, 0x4c, 0x92, 0x9f, 0xc1, 0x58, 0x35, 0xc7, 0x1a, - 0xba, 0xb7, 0xee, 0x54, 0x56, 0xa9, 0x31, 0x6b, 0x09, 0x71, 0xe4, 0x10, 0x62, 0x7b, 0x58, 0xdf, - 0x3d, 0xec, 0xa7, 0xe0, 0x23, 0xb3, 0x36, 0x2a, 0xfa, 0xb5, 0x9e, 0x35, 0xff, 0x6a, 0xab, 0x68, - 0x01, 0x5b, 0xbd, 0x1d, 0xdb, 0x9d, 0xbc, 0xfe, 0x4e, 0x5d, 0xa3, 0x07, 0xa6, 0xb1, 0xb1, 0x39, - 0x4a, 0x9e, 0xf2, 0xb8, 0xe2, 0x89, 0xa9, 0xba, 0x16, 0x5b, 0xb2, 0x18, 0xb5, 0x64, 0x11, 0xfd, - 0xd9, 0xeb, 0x76, 0x52, 0x11, 0x60, 0xd1, 0xc6, 0xf9, 0x6a, 0xc5, 0xb2, 0xc4, 0x6c, 0x66, 0x21, - 0x66, 0x32, 0xb9, 0x34, 0x9b, 0x0d, 0x92, 0x4b, 0xc4, 0xb2, 0x30, 0x77, 0x3a, 0x90, 0x05, 0x56, - 0xd3, 0x8a, 0xb3, 0xb2, 0x96, 0x7c, 0xc5, 0xb3, 0xca, 0xec, 0xe2, 0x8a, 0xc8, 0x87, 0xb0, 0x51, - 0xb1, 0xc5, 0xd7, 0x18, 0x83, 0xb9, 0xdb, 0x8a, 0x2d, 0x9e, 0xf1, 0x86, 0x7c, 0x1b, 0x02, 0xc5, - 0xa0, 0x4a, 0xa5, 0x2f, 0x78, 0xa2, 0x04, 0xcf, 0x78, 0x13, 0xfd, 0x6d, 0x00, 0xe3, 0x33, 0x2e, - 0xaf, 0xb8, 0x7c, 0xaf, 0x37, 0xdb, 0x9d, 0x94, 0x86, 0xef, 0x98, 0x94, 0x46, 0xeb, 0x27, 0x25, - 0xbf, 0x9b, 0x94, 0xee, 0x83, 0x7f, 0x26, 0xe3, 0xe3, 0x23, 0x15, 0xd1, 0x90, 0x6a, 0x80, 0xf5, - 0xb9, 0x1f, 0x57, 0xe2, 0x8a, 0x9b, 0xf1, 0xc9, 0xa0, 0x3b, 0x4f, 0xf9, 0x64, 0xcd, 0xcc, 0xf2, - 0xbf, 0x4e, 0x51, 0xb6, 0x69, 0xc1, 0x69, 0xda, 0x08, 0x36, 0x71, 0x94, 0x4a, 0x58, 0xc5, 0x9e, - 0x9e, 0xbd, 0x7c, 0x61, 0xe7, 0x27, 0x57, 0x16, 0xfd, 0xc9, 0x83, 0xf1, 0x09, 0x6b, 0xf2, 0xba, - 0xba, 0x53, 0xff, 0x33, 0x98, 0xee, 0x17, 0x45, 0x2a, 0xe2, 0x5e, 0xcf, 0x3b, 0x22, 0xb4, 0x78, - 0xee, 0xdc, 0xa3, 0xce, 0xa1, 0x2b, 0xc2, 0x27, 0xe6, 0x50, 0x8d, 0x45, 0x7a, 0xc6, 0x71, 0x9e, - 0x18, 0x3d, 0x0d, 0x29, 0x25, 0x26, 0x7b, 0xbf, 0xae, 0xf2, 0x79, 0x9a, 0x5f, 0xab, 0xac, 0x4e, - 0x68, 0x8b, 0xa3, 0x7f, 0x0f, 0x60, 0xf4, 0x4d, 0x8d, 0x32, 0x9b, 0xe0, 0x09, 0x53, 0x54, 0x9e, - 0x68, 0x07, 0x9b, 0x0d, 0x67, 0xb0, 0x09, 0x61, 0xa3, 0x91, 0x2c, 0x5b, 0xf0, 0x32, 0x9c, 0x28, - 0x5e, 0xb3, 0x50, 0x69, 0x54, 0x07, 0xeb, 0x89, 0x26, 0xa0, 0x16, 0xb6, 0x1d, 0x09, 0x4e, 0x47, - 0xfe, 0xd4, 0x0c, 0x3f, 0xd3, 0xdb, 0xe3, 0xc2, 0xba, 0x99, 0xe7, 0xff, 0xf7, 0x8e, 0xff, 0x71, - 0x00, 0x7e, 0xdb, 0xbc, 0x87, 0xfd, 0xe6, 0x3d, 0xec, 0x9a, 0xf7, 0xe8, 0xc0, 0x36, 0xef, 0xd1, - 0x01, 0x62, 0x7a, 0x6a, 0x9b, 0x97, 0x9e, 0xe2, 0x65, 0x3d, 0x91, 0x79, 0x5d, 0x1c, 0x34, 0xfa, - 0x56, 0x03, 0xda, 0x62, 0xac, 0xf8, 0xdf, 0x2c, 0xb9, 0x34, 0xa9, 0x0e, 0xa8, 0x41, 0xd8, 0x1f, - 0x27, 0x8a, 0xea, 0x74, 0x72, 0x35, 0x20, 0x3f, 0x00, 0x9f, 0x62, 0xf2, 0x54, 0x86, 0x7b, 0xf7, - 0xa2, 0xc4, 0x54, 0x6b, 0xd1, 0xa9, 0xfe, 0x24, 0x32, 0x8d, 0x62, 0x3f, 0x90, 0x7e, 0x02, 0xe3, - 0xb3, 0xa5, 0x98, 0x57, 0x76, 0x84, 0xfc, 0x96, 0x43, 0x95, 0x62, 0xc5, 0x95, 0x8e, 0x1a, 0x93, - 0x75, 0xfd, 0x11, 0xbd, 0x82, 0xa0, 0x35, 0xec, 0x42, 0xf4, 0xdc, 0x10, 0x09, 0x8c, 0x5e, 0x67, - 0xa2, 0xb2, 0xb4, 0x81, 0x6b, 0x4c, 0xc0, 0xab, 0x9a, 0x65, 0x95, 0xa8, 0x1a, 0x4b, 0x1b, 0x16, - 0x47, 0x0f, 0xcd, 0x91, 0xd0, 0xdd, 0xeb, 0xa2, 0xe0, 0xd2, 0x50, 0x90, 0x06, 0x6a, 0x93, 0xfc, - 0x9a, 0xeb, 0xf7, 0x64, 0x48, 0x35, 0x88, 0x7e, 0x0b, 0xc1, 0x7e, 0xca, 0x65, 0x45, 0xeb, 0x94, - 0xaf, 0x7b, 0xe7, 0x55, 0xf3, 0x9a, 0x08, 0x70, 0xdd, 0xd1, 0xcd, 0xf0, 0x16, 0xdd, 0x3c, 0x63, - 0x05, 0x3b, 0x3e, 0x52, 0xb5, 0x3f, 0xa4, 0x06, 0x45, 0x7f, 0xf1, 0x60, 0x84, 0xbc, 0xe6, 0xb8, - 0x1e, 0xbd, 0x8b, 0x13, 0x4f, 0x65, 0x7e, 0x25, 0x12, 0x2e, 0xed, 0xe1, 0x2c, 0x56, 0x17, 0x11, - 0x2f, 0x79, 0x3b, 0x4e, 0x18, 0x84, 0xf5, 0x87, 0xdf, 0x54, 0xb6, 0xbf, 0x9c, 0xfa, 0x43, 0x31, - 0xd5, 0x4a, 0x1c, 0x19, 0xcf, 0xea, 0x82, 0xcb, 0xfd, 0x64, 0x25, 0xec, 0xac, 0xe5, 0x48, 0xa2, - 0xaf, 0xf4, 0x57, 0xda, 0x1d, 0x76, 0xf4, 0xd6, 0x7f, 0xd1, 0xdd, 0x8e, 0x3c, 0xfa, 0xab, 0x07, - 0x1b, 0xcf, 0xcd, 0x6c, 0xe7, 0x9e, 0xc2, 0x7b, 0xeb, 0x29, 0x06, 0xbd, 0x53, 0xec, 0xc1, 0x7d, - 0x6b, 0xd3, 0xdb, 0x5f, 0x67, 0x61, 0xad, 0xce, 0x64, 0x74, 0xd4, 0x5e, 0xd6, 0xfb, 0x7c, 0xa4, - 0x9d, 0xf7, 0x6d, 0xd6, 0x5d, 0xf8, 0x9d, 0x5b, 0x99, 0xc1, 0xd4, 0x7e, 0x9c, 0xe6, 0xa9, 0x7d, - 0xac, 0x5c, 0x51, 0xb4, 0x07, 0xe3, 0xc3, 0x3c, 0x9b, 0x8b, 0x05, 0xd9, 0x85, 0xd1, 0x7e, 0x5d, - 0x2d, 0x95, 0xc7, 0xe9, 0xde, 0x7d, 0x87, 0x0c, 0xea, 0x6a, 0xa9, 0x6d, 0xa8, 0xb2, 0x88, 0xbe, - 0x00, 0xe8, 0x64, 0xf8, 0xe2, 0x74, 0xb7, 0xf1, 0x82, 0x5f, 0x63, 0xc9, 0x94, 0x66, 0xb4, 0x5f, - 0xa3, 0x89, 0x6a, 0x20, 0xee, 0x39, 0x8c, 0x97, 0x4f, 0x60, 0xdb, 0x95, 0xb6, 0x27, 0xbb, 0x25, - 0x25, 0xbf, 0x84, 0xe0, 0x24, 0x5f, 0x5c, 0x08, 0x6e, 0xbb, 0x61, 0xba, 0xf7, 0x91, 0xf3, 0x81, - 0x66, 0x55, 0x26, 0xde, 0xce, 0x36, 0x7a, 0x0c, 0xf7, 0x6e, 0x69, 0xc9, 0x43, 0xe4, 0x32, 0x9c, - 0xd5, 0xf5, 0xb0, 0xf9, 0x36, 0x4f, 0x68, 0x41, 0xad, 0x65, 0xd4, 0xf4, 0xfc, 0xa0, 0xac, 0xcd, - 0xbc, 0x77, 0xab, 0x1f, 0xf2, 0x52, 0xb4, 0x2f, 0xa0, 0x4f, 0x5b, 0x4c, 0x7e, 0x01, 0xc1, 0xa3, - 0x2c, 0xce, 0x13, 0x91, 0x2d, 0xec, 0x20, 0x18, 0xf6, 0xbe, 0x46, 0xeb, 0x55, 0x66, 0x0d, 0x68, - 0x67, 0x1a, 0xbd, 0x80, 0xed, 0xbe, 0x72, 0xed, 0xc8, 0xdd, 0x8e, 0xe9, 0x03, 0x67, 0x4c, 0x6f, - 0x63, 0x1c, 0x3a, 0x95, 0xff, 0x25, 0x04, 0x07, 0xb5, 0x48, 0x93, 0xe3, 0x6c, 0x9e, 0x23, 0xb1, - 0x5f, 0x70, 0x59, 0x76, 0x9d, 0x63, 0x21, 0x16, 0x3e, 0x72, 0x7c, 0xcb, 0x66, 0x06, 0x5d, 0x8e, - 0xd5, 0x9f, 0x50, 0x0f, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xff, 0x9a, 0x3e, 0x9f, 0x96, 0x12, - 0x00, 0x00, -} diff --git a/chronograf/bolt/internal/internal.proto b/chronograf/bolt/internal/internal.proto deleted file mode 100644 index b8d94c3761c..00000000000 --- a/chronograf/bolt/internal/internal.proto +++ /dev/null @@ -1,247 +0,0 @@ -syntax = "proto3"; -package internal; - -message Source { - int64 ID = 1; // ID is the unique ID of the source - string Name = 2; // Name is the user-defined name for the source - string Type = 3; // Type specifies which kinds of source (enterprise vs oss) - string Username = 4; // Username is the username to connect to the source - string Password = 5; - string URL = 6; // URL are the connections to the source - bool Default = 7; // Flags an source as the default. - string Telegraf = 8; // Telegraf is the db telegraf is written to. By default it is "telegraf" - bool InsecureSkipVerify = 9; // InsecureSkipVerify accepts any certificate from the influx server - string MetaURL = 10; // MetaURL is the connection URL for the meta node. - string SharedSecret = 11; // SharedSecret signs the optional InfluxDB JWT Authorization - string Organization = 12; // Organization is the organization ID that resource belongs to - string Role = 13; // Role is the name of the miniumum role that a user must possess to access the resource - string DefaultRP = 14; // DefaultRP is the default retention policy used in database queries to this source -} - -message Dashboard { - int64 ID = 1; // ID is the unique ID of the dashboard - string Name = 2; // Name is the user-defined name of the dashboard - repeated DashboardCell cells = 3; // a representation of all visual data required for rendering the dashboard - repeated Template templates = 4; // Templates replace template variables within InfluxQL - string Organization = 5; // Organization is the organization ID that resource belongs to -} - -message DashboardCell { - int32 x = 1; // X-coordinate of Cell in the Dashboard - int32 y = 2; // Y-coordinate of Cell in the Dashboard - int32 w = 3; // Width of Cell in the Dashboard - int32 h = 4; // Height of Cell in the Dashboard - repeated Query queries = 5; // Time-series data queries for Dashboard - string name = 6; // User-facing name for this Dashboard - string type = 7; // Dashboard visualization type - string ID = 8; // id is the unique id of the dashboard. MIGRATED FIELD added in 1.2.0-beta6 - map axes = 9; // Axes represent the graphical viewport for a cell's visualizations - repeated Color colors = 10; // Colors represent encoding data values to color - Legend legend = 11; // Legend is summary information for a cell - TableOptions tableOptions = 12; // TableOptions for visualization of cell with type 'table' - repeated RenamableField fieldOptions = 13; // Options for each of the fields returned in a cell - string timeFormat = 14; // format for time - DecimalPlaces decimalPlaces = 15; // Represents how precise the values of this field should be -} - -message DecimalPlaces { - bool isEnforced = 1; // whether decimal places should be enforced - int32 digits = 2; // the number of digits to display after decical point -} - -message TableOptions { - reserved 1; - bool verticalTimeAxis = 2; // time axis should be a column not row - RenamableField sortBy = 3; // which column should a table be sorted by - string wrapping = 4; // option for text wrapping - reserved 5; - bool fixFirstColumn = 6; // first column should be fixed/frozen -} - -message RenamableField { - string internalName = 1; // name of column - string displayName = 2; // what column is renamed to - bool visible = 3; // Represents whether RenamableField is visible -} - -message Color { - string ID = 1; // ID is the unique id of the cell color - string Type = 2; // Type is how the color is used. Accepted (min,max,threshold) - string Hex = 3; // Hex is the hex number of the color - string Name = 4; // Name is the user-facing name of the hex color - string Value = 5; // Value is the data value mapped to this color -} - -message Legend { - string Type = 1; // Type is how the legend is used - string Orientation = 2; // Orientation is the location of the legend on the cell -} - -message Axis { - repeated int64 legacyBounds = 1; // legacyBounds are an ordered 2-tuple consisting of lower and upper axis extents, respectively - repeated string bounds = 2; // bounds are an arbitrary list of client-defined bounds. - string label = 3; // label is a description of this axis - string prefix = 4; // specifies the prefix for axis values - string suffix = 5; // specifies the suffix for axis values - string base = 6; // defines the base for axis values - string scale = 7; // represents the magnitude of the numbers on this axis -} - -message Template { - string ID = 1; // ID is the unique ID associated with this template - string temp_var = 2; - repeated TemplateValue values = 3; - string type = 4; // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases - string label = 5; // Label is a user-facing description of the Template - TemplateQuery query = 6; // Query is used to generate the choices for a template -} - -message TemplateValue { - string type = 1; // Type can be tagKey, tagValue, fieldKey, csv, map, measurement, database, constant - string value = 2; // Value is the specific value used to replace a template in an InfluxQL query - bool selected = 3; // Selected states that this variable has been picked to use for replacement - string key = 4; // Key is the key for a specific Value if the Template Type is map (optional) -} - -message TemplateQuery { - string command = 1; // Command is the query itself - string db = 2; // DB the database for the query (optional) - string rp = 3; // RP is a retention policy and optional; - string measurement = 4; // Measurement is the optinally selected measurement for the query - string tag_key = 5; // TagKey is the optionally selected tag key for the query - string field_key = 6; // FieldKey is the optionally selected field key for the query -} - -message Server { - int64 ID = 1; // ID is the unique ID of the server - string Name = 2; // Name is the user-defined name for the server - string Username = 3; // Username is the username to connect to the server - string Password = 4; - string URL = 5; // URL is the path to the server - int64 SrcID = 6; // SrcID is the ID of the data source - bool Active = 7; // is this the currently active server for the source - string Organization = 8; // Organization is the organization ID that resource belongs to - bool InsecureSkipVerify = 9; // InsecureSkipVerify accepts any certificate from the client - string Type = 10; // Type is the kind of the server (e.g. flux) - string MetadataJSON = 11; // JSON byte representation of the metadata -} - -message Layout { - string ID = 1; // ID is the unique ID of the layout. - string Application = 2; // Application is the user facing name of this Layout. - string Measurement = 3; // Measurement is the descriptive name of the time series data. - repeated Cell Cells = 4; // Cells are the individual visualization elements. - bool Autoflow = 5; // Autoflow indicates whether the frontend should layout the cells automatically. -} - -message Cell { - int32 x = 1; // X-coordinate of Cell in the Layout - int32 y = 2; // Y-coordinate of Cell in the Layout - int32 w = 3; // Width of Cell in the Layout - int32 h = 4; // Height of Cell in the Layout - repeated Query queries = 5; // Time-series data queries for Cell. - string i = 6; // Unique identifier for the cell - string name = 7; // User-facing name for this cell - repeated int64 yranges = 8; // Limits of the y-axes - repeated string ylabels = 9; // Labels of the y-axes - string type = 10; // Cell visualization type - map axes = 11; // Axes represent the graphical viewport for a cell's visualizations -} - -message Query { - string Command = 1; // Command is the query itself - string DB = 2; // DB the database for the query (optional) - string RP = 3; // RP is a retention policy and optional; - repeated string GroupBys = 4; // GroupBys define the groups to combine in the query - repeated string Wheres = 5; // Wheres define the restrictions on the query - string Label = 6; // Label is the name of the Y-Axis - Range Range = 7; // Range is the upper and lower bound of the Y-Axis - string Source = 8; // Source is the optional URI to the data source - repeated TimeShift Shifts = 9; // TimeShift represents a shift to apply to an influxql query's time range - string Type = 10; -} - -message TimeShift { - string Label = 1; // Label user facing description - string Unit = 2; // Unit influxql time unit representation i.e. ms, s, m, h, d - string Quantity = 3; // Quantity number of units -} - -message Range { - int64 Upper = 1; // Upper is the upper-bound of the range - int64 Lower = 2; // Lower is the lower-bound of the range -} - -message AlertRule { - string ID = 1; // ID is the unique ID of this alert rule - string JSON = 2; // JSON byte representation of the alert - int64 SrcID = 3; // SrcID is the id of the source this alert is associated with - int64 KapaID = 4; // KapaID is the id of the kapacitor this alert is associated with -} - -message User { - uint64 ID = 1; // ID is the unique ID of this user - string Name = 2; // Name is the user's login name - string Provider = 3; // Provider is the provider that certifies and issues this user's authentication, e.g. GitHub - string Scheme = 4; // Scheme is the scheme used to perform this user's authentication, e.g. OAuth2 or LDAP - repeated Role Roles = 5; // Roles is set of roles a user has - bool SuperAdmin = 6; // SuperAdmin is bool that specifies whether a user is a super admin -} - -message Role { - string Organization = 1; // Organization is the ID of the organization that this user has a role in - string Name = 2; // Name is the name of the role of this user in the respective organization -} - -message Mapping { - string Provider = 1; // Provider is the provider that certifies and issues this user's authentication, e.g. GitHub - string Scheme = 2; // Scheme is the scheme used to perform this user's authentication, e.g. OAuth2 or LDAP - string ProviderOrganization = 3; // ProviderOrganization is the group or organizations that you are a part of in an auth provider - string ID = 4; // ID is the unique ID for the mapping - string Organization = 5; // Organization is the organization ID that resource belongs to -} - -message Organization { - string ID = 1; // ID is the unique ID of the organization - string Name = 2; // Name is the organization's name - string DefaultRole = 3; // DefaultRole is the name of the role that is the default for any users added to the organization -} - -message Config { - AuthConfig Auth = 1; // Auth is the configuration for options that auth related -} - -message AuthConfig { - bool SuperAdminNewUsers = 1; // SuperAdminNewUsers configuration option that specifies which users will auto become super admin -} - -message OrganizationConfig { - string OrganizationID = 1; // OrganizationID is the ID of the organization this config belogs to - LogViewerConfig LogViewer = 2; // LogViewer is the organization configuration for log viewer -} - -message LogViewerConfig { - repeated LogViewerColumn Columns = 1; // Columns is the array of columns in the log viewer -} - -message LogViewerColumn { - string Name = 1; // Name is the unique identifier of the log viewer column - int32 Position = 2; // Position is the position of the column in the log viewer's array of columns - repeated ColumnEncoding Encodings = 3; // Encodings is the array of encoded properties associated with a log viewer column -} - -message ColumnEncoding { - string Type = 1; // Type is the purpose of the encoding, for example: severity color - string Value = 2; // Value is what the encoding corresponds to - string Name = 3; // Name is the optional encoding name -} - -message BuildInfo { - string Version = 1; // Version is a descriptive git SHA identifier - string Commit = 2; // Commit is an abbreviated SHA -} - -// The following is a vim modeline, it autoconfigures vim to have the -// appropriate tabbing and whitespace management to edit this file -// -// vim: ai:ts=4:noet:sts=4 diff --git a/chronograf/bolt/internal/internal_test.go b/chronograf/bolt/internal/internal_test.go deleted file mode 100644 index 363ed42533d..00000000000 --- a/chronograf/bolt/internal/internal_test.go +++ /dev/null @@ -1,488 +0,0 @@ -package internal_test - -import ( - "reflect" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" -) - -func TestMarshalSource(t *testing.T) { - v := chronograf.Source{ - ID: 12, - Name: "Fountain of Truth", - Type: "influx", - Username: "docbrown", - Password: "1 point twenty-one g1g@w@tts", - URL: "http://twin-pines.mall.io:8086", - MetaURL: "http://twin-pines.meta.io:8086", - Default: true, - Telegraf: "telegraf", - } - - var vv chronograf.Source - if buf, err := internal.MarshalSource(v); err != nil { - t.Fatal(err) - } else if err := internal.UnmarshalSource(buf, &vv); err != nil { - t.Fatal(err) - } else if !reflect.DeepEqual(v, vv) { - t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, v) - } - - // Test if the new insecureskipverify works - v.InsecureSkipVerify = true - if buf, err := internal.MarshalSource(v); err != nil { - t.Fatal(err) - } else if err := internal.UnmarshalSource(buf, &vv); err != nil { - t.Fatal(err) - } else if !reflect.DeepEqual(v, vv) { - t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, v) - } -} -func TestMarshalSourceWithSecret(t *testing.T) { - v := chronograf.Source{ - ID: 12, - Name: "Fountain of Truth", - Type: "influx", - Username: "docbrown", - SharedSecret: "hunter2s", - URL: "http://twin-pines.mall.io:8086", - MetaURL: "http://twin-pines.meta.io:8086", - Default: true, - Telegraf: "telegraf", - } - - var vv chronograf.Source - if buf, err := internal.MarshalSource(v); err != nil { - t.Fatal(err) - } else if err := internal.UnmarshalSource(buf, &vv); err != nil { - t.Fatal(err) - } else if !reflect.DeepEqual(v, vv) { - t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, v) - } - - // Test if the new insecureskipverify works - v.InsecureSkipVerify = true - if buf, err := internal.MarshalSource(v); err != nil { - t.Fatal(err) - } else if err := internal.UnmarshalSource(buf, &vv); err != nil { - t.Fatal(err) - } else if !reflect.DeepEqual(v, vv) { - t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, v) - } -} - -func TestMarshalServer(t *testing.T) { - v := chronograf.Server{ - ID: 12, - SrcID: 2, - Name: "Fountain of Truth", - Username: "docbrown", - Password: "1 point twenty-one g1g@w@tts", - URL: "http://oldmanpeabody.mall.io:9092", - InsecureSkipVerify: true, - } - - var vv chronograf.Server - if buf, err := internal.MarshalServer(v); err != nil { - t.Fatal(err) - } else if err := internal.UnmarshalServer(buf, &vv); err != nil { - t.Fatal(err) - } else if !reflect.DeepEqual(v, vv) { - t.Fatalf("source protobuf copy error: got %#v, expected %#v", vv, v) - } -} - -func TestMarshalLayout(t *testing.T) { - layout := chronograf.Layout{ - ID: "id", - Measurement: "measurement", - Application: "app", - Cells: []chronograf.Cell{ - { - X: 1, - Y: 1, - W: 4, - H: 4, - I: "anotherid", - Type: "line", - Name: "cell1", - Axes: map[string]chronograf.Axis{ - "y": chronograf.Axis{ - Bounds: []string{"0", "100"}, - Label: "foo", - }, - }, - Queries: []chronograf.Query{ - { - Range: &chronograf.Range{ - Lower: 1, - Upper: 2, - }, - Label: "y1", - Command: "select mean(usage_user) as usage_user from cpu", - Wheres: []string{ - `"host"="myhost"`, - }, - GroupBys: []string{ - `"cpu"`, - }, - }, - }, - }, - }, - } - - var vv chronograf.Layout - if buf, err := internal.MarshalLayout(layout); err != nil { - t.Fatal(err) - } else if err := internal.UnmarshalLayout(buf, &vv); err != nil { - t.Fatal(err) - } else if !cmp.Equal(layout, vv) { - t.Fatal("source protobuf copy error: diff:\n", cmp.Diff(layout, vv)) - } -} - -func Test_MarshalDashboard(t *testing.T) { - dashboard := chronograf.Dashboard{ - ID: 1, - Cells: []chronograf.DashboardCell{ - { - ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", - X: 0, - Y: 0, - W: 4, - H: 4, - Name: "Super awesome query", - Queries: []chronograf.DashboardQuery{ - { - Command: "select * from cpu", - Label: "CPU Utilization", - Range: &chronograf.Range{ - Upper: int64(100), - }, - Source: "/chronograf/v1/sources/1", - Shifts: []chronograf.TimeShift{}, - }, - }, - Axes: map[string]chronograf.Axis{ - "y": chronograf.Axis{ - Bounds: []string{"0", "3", "1-7", "foo"}, - Label: "foo", - Prefix: "M", - Suffix: "m", - Base: "2", - Scale: "roflscale", - }, - }, - Type: "line", - CellColors: []chronograf.CellColor{ - { - ID: "myid", - Type: "min", - Hex: "#234567", - Name: "Laser", - Value: "0", - }, - { - ID: "id2", - Type: "max", - Hex: "#876543", - Name: "Solitude", - Value: "100", - }, - }, - TableOptions: chronograf.TableOptions{}, - FieldOptions: []chronograf.RenamableField{}, - TimeFormat: "", - }, - }, - Templates: []chronograf.Template{}, - Name: "Dashboard", - } - - var actual chronograf.Dashboard - if buf, err := internal.MarshalDashboard(dashboard); err != nil { - t.Fatal("Error marshaling dashboard: err", err) - } else if err := internal.UnmarshalDashboard(buf, &actual); err != nil { - t.Fatal("Error unmarshalling dashboard: err:", err) - } else if !cmp.Equal(dashboard, actual) { - t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(dashboard, actual)) - } -} - -func Test_MarshalDashboard_WithLegacyBounds(t *testing.T) { - dashboard := chronograf.Dashboard{ - ID: 1, - Cells: []chronograf.DashboardCell{ - { - ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", - X: 0, - Y: 0, - W: 4, - H: 4, - Name: "Super awesome query", - Queries: []chronograf.DashboardQuery{ - { - Command: "select * from cpu", - Label: "CPU Utilization", - Range: &chronograf.Range{ - Upper: int64(100), - }, - Shifts: []chronograf.TimeShift{}, - }, - }, - Axes: map[string]chronograf.Axis{ - "y": chronograf.Axis{ - LegacyBounds: [2]int64{0, 5}, - }, - }, - CellColors: []chronograf.CellColor{ - { - ID: "myid", - Type: "min", - Hex: "#234567", - Name: "Laser", - Value: "0", - }, - { - ID: "id2", - Type: "max", - Hex: "#876543", - Name: "Solitude", - Value: "100", - }, - }, - Legend: chronograf.Legend{ - Type: "static", - Orientation: "bottom", - }, - TableOptions: chronograf.TableOptions{}, - TimeFormat: "MM:DD:YYYY", - FieldOptions: []chronograf.RenamableField{}, - Type: "line", - }, - }, - Templates: []chronograf.Template{}, - Name: "Dashboard", - } - - expected := chronograf.Dashboard{ - ID: 1, - Cells: []chronograf.DashboardCell{ - { - ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", - X: 0, - Y: 0, - W: 4, - H: 4, - Name: "Super awesome query", - Queries: []chronograf.DashboardQuery{ - { - Command: "select * from cpu", - Label: "CPU Utilization", - Range: &chronograf.Range{ - Upper: int64(100), - }, - Shifts: []chronograf.TimeShift{}, - }, - }, - Axes: map[string]chronograf.Axis{ - "y": chronograf.Axis{ - Bounds: []string{}, - Base: "10", - Scale: "linear", - }, - }, - CellColors: []chronograf.CellColor{ - { - ID: "myid", - Type: "min", - Hex: "#234567", - Name: "Laser", - Value: "0", - }, - { - ID: "id2", - Type: "max", - Hex: "#876543", - Name: "Solitude", - Value: "100", - }, - }, - Legend: chronograf.Legend{ - Type: "static", - Orientation: "bottom", - }, - TableOptions: chronograf.TableOptions{}, - FieldOptions: []chronograf.RenamableField{}, - TimeFormat: "MM:DD:YYYY", - Type: "line", - }, - }, - Templates: []chronograf.Template{}, - Name: "Dashboard", - } - - var actual chronograf.Dashboard - if buf, err := internal.MarshalDashboard(dashboard); err != nil { - t.Fatal("Error marshaling dashboard: err", err) - } else if err := internal.UnmarshalDashboard(buf, &actual); err != nil { - t.Fatal("Error unmarshalling dashboard: err:", err) - } else if !cmp.Equal(expected, actual) { - t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual)) - } -} - -func Test_MarshalDashboard_WithEmptyLegacyBounds(t *testing.T) { - dashboard := chronograf.Dashboard{ - ID: 1, - Cells: []chronograf.DashboardCell{ - { - ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", - X: 0, - Y: 0, - W: 4, - H: 4, - Name: "Super awesome query", - Queries: []chronograf.DashboardQuery{ - { - Command: "select * from cpu", - Label: "CPU Utilization", - Range: &chronograf.Range{ - Upper: int64(100), - }, - Shifts: []chronograf.TimeShift{}, - }, - }, - Axes: map[string]chronograf.Axis{ - "y": chronograf.Axis{ - LegacyBounds: [2]int64{}, - }, - }, - CellColors: []chronograf.CellColor{ - { - ID: "myid", - Type: "min", - Hex: "#234567", - Name: "Laser", - Value: "0", - }, - { - ID: "id2", - Type: "max", - Hex: "#876543", - Name: "Solitude", - Value: "100", - }, - }, - Type: "line", - TableOptions: chronograf.TableOptions{}, - FieldOptions: []chronograf.RenamableField{}, - TimeFormat: "MM:DD:YYYY", - }, - }, - Templates: []chronograf.Template{}, - Name: "Dashboard", - } - - expected := chronograf.Dashboard{ - ID: 1, - Cells: []chronograf.DashboardCell{ - { - ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", - X: 0, - Y: 0, - W: 4, - H: 4, - Name: "Super awesome query", - Queries: []chronograf.DashboardQuery{ - { - Command: "select * from cpu", - Label: "CPU Utilization", - Range: &chronograf.Range{ - Upper: int64(100), - }, - Shifts: []chronograf.TimeShift{}, - }, - }, - Axes: map[string]chronograf.Axis{ - "y": chronograf.Axis{ - Bounds: []string{}, - Base: "10", - Scale: "linear", - }, - }, - CellColors: []chronograf.CellColor{ - { - ID: "myid", - Type: "min", - Hex: "#234567", - Name: "Laser", - Value: "0", - }, - { - ID: "id2", - Type: "max", - Hex: "#876543", - Name: "Solitude", - Value: "100", - }, - }, - TableOptions: chronograf.TableOptions{}, - FieldOptions: []chronograf.RenamableField{}, - TimeFormat: "MM:DD:YYYY", - Type: "line", - }, - }, - Templates: []chronograf.Template{}, - Name: "Dashboard", - } - - var actual chronograf.Dashboard - if buf, err := internal.MarshalDashboard(dashboard); err != nil { - t.Fatal("Error marshaling dashboard: err", err) - } else if err := internal.UnmarshalDashboard(buf, &actual); err != nil { - t.Fatal("Error unmarshalling dashboard: err:", err) - } else if !cmp.Equal(expected, actual) { - t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual)) - } -} - -func Test_MarshalDashboard_WithEmptyCellType(t *testing.T) { - dashboard := chronograf.Dashboard{ - ID: 1, - Cells: []chronograf.DashboardCell{ - { - ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", - }, - }, - } - - expected := chronograf.Dashboard{ - ID: 1, - Cells: []chronograf.DashboardCell{ - { - ID: "9b5367de-c552-4322-a9e8-7f384cbd235c", - Type: "line", - Queries: []chronograf.DashboardQuery{}, - Axes: map[string]chronograf.Axis{}, - CellColors: []chronograf.CellColor{}, - TableOptions: chronograf.TableOptions{}, - FieldOptions: []chronograf.RenamableField{}, - }, - }, - Templates: []chronograf.Template{}, - } - - var actual chronograf.Dashboard - if buf, err := internal.MarshalDashboard(dashboard); err != nil { - t.Fatal("Error marshaling dashboard: err", err) - } else if err := internal.UnmarshalDashboard(buf, &actual); err != nil { - t.Fatal("Error unmarshalling dashboard: err:", err) - } else if !cmp.Equal(expected, actual) { - t.Fatalf("Dashboard protobuf copy error: diff follows:\n%s", cmp.Diff(expected, actual)) - } -} diff --git a/chronograf/bolt/layouts.go b/chronograf/bolt/layouts.go deleted file mode 100644 index 81ad5e0cf88..00000000000 --- a/chronograf/bolt/layouts.go +++ /dev/null @@ -1,128 +0,0 @@ -package bolt - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - bolt "go.etcd.io/bbolt" -) - -// Ensure LayoutsStore implements chronograf.LayoutsStore. -var _ chronograf.LayoutsStore = &LayoutsStore{} - -// LayoutsBucket is the bolt bucket layouts are stored in -var LayoutsBucket = []byte("Layout") - -// LayoutsStore is the bolt implementation to store layouts -type LayoutsStore struct { - client *Client - IDs chronograf.ID -} - -func (s *LayoutsStore) Migrate(ctx context.Context) error { - return nil -} - -// All returns all known layouts -func (s *LayoutsStore) All(ctx context.Context) ([]chronograf.Layout, error) { - var srcs []chronograf.Layout - if err := s.client.db.View(func(tx *bolt.Tx) error { - if err := tx.Bucket(LayoutsBucket).ForEach(func(k, v []byte) error { - var src chronograf.Layout - if err := internal.UnmarshalLayout(v, &src); err != nil { - return err - } - srcs = append(srcs, src) - return nil - }); err != nil { - return err - } - return nil - }); err != nil { - return nil, err - } - - return srcs, nil - -} - -// Add creates a new Layout in the LayoutsStore. -func (s *LayoutsStore) Add(ctx context.Context, src chronograf.Layout) (chronograf.Layout, error) { - if err := s.client.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(LayoutsBucket) - id, err := s.IDs.Generate() - if err != nil { - return err - } - - src.ID = id - if v, err := internal.MarshalLayout(src); err != nil { - return err - } else if err := b.Put([]byte(src.ID), v); err != nil { - return err - } - return nil - }); err != nil { - return chronograf.Layout{}, err - } - - return src, nil -} - -// Delete removes the Layout from the LayoutsStore -func (s *LayoutsStore) Delete(ctx context.Context, src chronograf.Layout) error { - _, err := s.Get(ctx, src.ID) - if err != nil { - return err - } - if err := s.client.db.Update(func(tx *bolt.Tx) error { - if err := tx.Bucket(LayoutsBucket).Delete([]byte(src.ID)); err != nil { - return err - } - return nil - }); err != nil { - return err - } - - return nil -} - -// Get returns a Layout if the id exists. -func (s *LayoutsStore) Get(ctx context.Context, id string) (chronograf.Layout, error) { - var src chronograf.Layout - if err := s.client.db.View(func(tx *bolt.Tx) error { - if v := tx.Bucket(LayoutsBucket).Get([]byte(id)); v == nil { - return chronograf.ErrLayoutNotFound - } else if err := internal.UnmarshalLayout(v, &src); err != nil { - return err - } - return nil - }); err != nil { - return chronograf.Layout{}, err - } - - return src, nil -} - -// Update a Layout -func (s *LayoutsStore) Update(ctx context.Context, src chronograf.Layout) error { - if err := s.client.db.Update(func(tx *bolt.Tx) error { - // Get an existing layout with the same ID. - b := tx.Bucket(LayoutsBucket) - if v := b.Get([]byte(src.ID)); v == nil { - return chronograf.ErrLayoutNotFound - } - - if v, err := internal.MarshalLayout(src); err != nil { - return err - } else if err := b.Put([]byte(src.ID), v); err != nil { - return err - } - return nil - }); err != nil { - return err - } - - return nil -} diff --git a/chronograf/bolt/mapping.go b/chronograf/bolt/mapping.go deleted file mode 100644 index 6cc224bd4db..00000000000 --- a/chronograf/bolt/mapping.go +++ /dev/null @@ -1,128 +0,0 @@ -package bolt - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - bolt "go.etcd.io/bbolt" -) - -// Ensure MappingsStore implements chronograf.MappingsStore. -var _ chronograf.MappingsStore = &MappingsStore{} - -var ( - // MappingsBucket is the bucket where organizations are stored. - MappingsBucket = []byte("MappingsV1") -) - -// MappingsStore uses bolt to store and retrieve Mappings -type MappingsStore struct { - client *Client -} - -// Migrate sets the default organization at runtime -func (s *MappingsStore) Migrate(ctx context.Context) error { - return nil -} - -// Add creates a new Mapping in the MappingsStore -func (s *MappingsStore) Add(ctx context.Context, o *chronograf.Mapping) (*chronograf.Mapping, error) { - err := s.client.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(MappingsBucket) - seq, err := b.NextSequence() - if err != nil { - return err - } - o.ID = fmt.Sprintf("%d", seq) - - v, err := internal.MarshalMapping(o) - if err != nil { - return err - } - - return b.Put([]byte(o.ID), v) - }) - - if err != nil { - return nil, err - } - - return o, nil -} - -// All returns all known organizations -func (s *MappingsStore) All(ctx context.Context) ([]chronograf.Mapping, error) { - var mappings []chronograf.Mapping - err := s.each(ctx, func(m *chronograf.Mapping) { - mappings = append(mappings, *m) - }) - - if err != nil { - return nil, err - } - - return mappings, nil -} - -// Delete the organization from MappingsStore -func (s *MappingsStore) Delete(ctx context.Context, o *chronograf.Mapping) error { - _, err := s.get(ctx, o.ID) - if err != nil { - return err - } - if err := s.client.db.Update(func(tx *bolt.Tx) error { - return tx.Bucket(MappingsBucket).Delete([]byte(o.ID)) - }); err != nil { - return err - } - return nil -} - -func (s *MappingsStore) get(ctx context.Context, id string) (*chronograf.Mapping, error) { - var o chronograf.Mapping - err := s.client.db.View(func(tx *bolt.Tx) error { - v := tx.Bucket(MappingsBucket).Get([]byte(id)) - if v == nil { - return chronograf.ErrMappingNotFound - } - return internal.UnmarshalMapping(v, &o) - }) - - if err != nil { - return nil, err - } - - return &o, nil -} - -func (s *MappingsStore) each(ctx context.Context, fn func(*chronograf.Mapping)) error { - return s.client.db.View(func(tx *bolt.Tx) error { - return tx.Bucket(MappingsBucket).ForEach(func(k, v []byte) error { - var m chronograf.Mapping - if err := internal.UnmarshalMapping(v, &m); err != nil { - return err - } - fn(&m) - return nil - }) - }) -} - -// Get returns a Mapping if the id exists. -func (s *MappingsStore) Get(ctx context.Context, id string) (*chronograf.Mapping, error) { - return s.get(ctx, id) -} - -// Update the organization in MappingsStore -func (s *MappingsStore) Update(ctx context.Context, o *chronograf.Mapping) error { - return s.client.db.Update(func(tx *bolt.Tx) error { - if v, err := internal.MarshalMapping(o); err != nil { - return err - } else if err := tx.Bucket(MappingsBucket).Put([]byte(o.ID), v); err != nil { - return err - } - return nil - }) -} diff --git a/chronograf/bolt/mapping_test.go b/chronograf/bolt/mapping_test.go deleted file mode 100644 index 27e695990f3..00000000000 --- a/chronograf/bolt/mapping_test.go +++ /dev/null @@ -1,480 +0,0 @@ -package bolt_test - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/influxdata/influxdb/v2/chronograf" -) - -var mappingCmpOptions = cmp.Options{ - cmpopts.IgnoreFields(chronograf.Mapping{}, "ID"), - cmpopts.EquateEmpty(), -} - -func TestMappingStore_Add(t *testing.T) { - type fields struct { - mappings []*chronograf.Mapping - } - type args struct { - mapping *chronograf.Mapping - } - type wants struct { - mapping *chronograf.Mapping - err error - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "default with wildcards", - args: args{ - mapping: &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - }, - wants: wants{ - mapping: &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - }, - }, - { - name: "simple", - args: args{ - mapping: &chronograf.Mapping{ - Organization: "default", - Provider: "github", - Scheme: "oauth2", - ProviderOrganization: "idk", - }, - }, - wants: wants{ - mapping: &chronograf.Mapping{ - Organization: "default", - Provider: "github", - Scheme: "oauth2", - ProviderOrganization: "idk", - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.MappingsStore - ctx := context.Background() - - for _, mapping := range tt.fields.mappings { - // YOLO database prepopulation - _, _ = s.Add(ctx, mapping) - } - - tt.args.mapping, err = s.Add(ctx, tt.args.mapping) - - if (err != nil) != (tt.wants.err != nil) { - t.Errorf("MappingsStore.Add() error = %v, want error %v", err, tt.wants.err) - return - } - - got, err := s.Get(ctx, tt.args.mapping.ID) - if err != nil { - t.Fatalf("failed to get mapping: %v", err) - return - } - if diff := cmp.Diff(got, tt.wants.mapping, mappingCmpOptions...); diff != "" { - t.Errorf("MappingStore.Add():\n-got/+want\ndiff %s", diff) - return - } - }) - } -} - -func TestMappingStore_All(t *testing.T) { - type fields struct { - mappings []*chronograf.Mapping - } - type wants struct { - mappings []chronograf.Mapping - err error - } - tests := []struct { - name string - fields fields - wants wants - }{ - { - name: "simple", - fields: fields{ - mappings: []*chronograf.Mapping{ - &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - ProviderOrganization: "*", - }, - }, - }, - wants: wants{ - mappings: []chronograf.Mapping{ - chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - ProviderOrganization: "*", - }, - chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.MappingsStore - ctx := context.Background() - - for _, mapping := range tt.fields.mappings { - // YOLO database prepopulation - _, _ = s.Add(ctx, mapping) - } - - got, err := s.All(ctx) - - if (err != nil) != (tt.wants.err != nil) { - t.Errorf("MappingsStore.All() error = %v, want error %v", err, tt.wants.err) - return - } - - if diff := cmp.Diff(got, tt.wants.mappings, mappingCmpOptions...); diff != "" { - t.Errorf("MappingStore.All():\n-got/+want\ndiff %s", diff) - return - } - }) - } -} - -func TestMappingStore_Delete(t *testing.T) { - type fields struct { - mappings []*chronograf.Mapping - } - type args struct { - mapping *chronograf.Mapping - } - type wants struct { - err error - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "simple", - fields: fields{ - mappings: []*chronograf.Mapping{ - &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - ProviderOrganization: "*", - }, - }, - }, - args: args{ - mapping: &chronograf.Mapping{ - ID: "1", - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - }, - wants: wants{ - err: nil, - }, - }, - { - name: "mapping not found", - fields: fields{ - mappings: []*chronograf.Mapping{ - &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - ProviderOrganization: "*", - }, - }, - }, - args: args{ - mapping: &chronograf.Mapping{ - ID: "0", - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - }, - wants: wants{ - err: chronograf.ErrMappingNotFound, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.MappingsStore - ctx := context.Background() - - for _, mapping := range tt.fields.mappings { - // YOLO database prepopulation - _, _ = s.Add(ctx, mapping) - } - - err = s.Delete(ctx, tt.args.mapping) - - if (err != nil) != (tt.wants.err != nil) { - t.Errorf("MappingsStore.Delete() error = %v, want error %v", err, tt.wants.err) - return - } - }) - } -} - -func TestMappingStore_Get(t *testing.T) { - type fields struct { - mappings []*chronograf.Mapping - } - type args struct { - mappingID string - } - type wants struct { - mapping *chronograf.Mapping - err error - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "simple", - fields: fields{ - mappings: []*chronograf.Mapping{ - &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - ProviderOrganization: "*", - }, - }, - }, - args: args{ - mappingID: "1", - }, - wants: wants{ - mapping: &chronograf.Mapping{ - ID: "1", - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - err: nil, - }, - }, - { - name: "mapping not found", - fields: fields{ - mappings: []*chronograf.Mapping{ - &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - ProviderOrganization: "*", - }, - }, - }, - args: args{ - mappingID: "0", - }, - wants: wants{ - err: chronograf.ErrMappingNotFound, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.MappingsStore - ctx := context.Background() - - for _, mapping := range tt.fields.mappings { - // YOLO database prepopulation - _, _ = s.Add(ctx, mapping) - } - - got, err := s.Get(ctx, tt.args.mappingID) - if (err != nil) != (tt.wants.err != nil) { - t.Errorf("MappingsStore.Get() error = %v, want error %v", err, tt.wants.err) - return - } - if diff := cmp.Diff(got, tt.wants.mapping, mappingCmpOptions...); diff != "" { - t.Errorf("MappingStore.Get():\n-got/+want\ndiff %s", diff) - return - } - }) - } -} - -func TestMappingStore_Update(t *testing.T) { - type fields struct { - mappings []*chronograf.Mapping - } - type args struct { - mapping *chronograf.Mapping - } - type wants struct { - mapping *chronograf.Mapping - err error - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "simple", - fields: fields{ - mappings: []*chronograf.Mapping{ - &chronograf.Mapping{ - Organization: "default", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - &chronograf.Mapping{ - Organization: "0", - Provider: "google", - Scheme: "ldap", - ProviderOrganization: "*", - }, - }, - }, - args: args{ - mapping: &chronograf.Mapping{ - ID: "1", - Organization: "default", - Provider: "cool", - Scheme: "it", - ProviderOrganization: "works", - }, - }, - wants: wants{ - mapping: &chronograf.Mapping{ - ID: "1", - Organization: "default", - Provider: "cool", - Scheme: "it", - ProviderOrganization: "works", - }, - err: nil, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.MappingsStore - ctx := context.Background() - - for _, mapping := range tt.fields.mappings { - // YOLO database prepopulation - _, _ = s.Add(ctx, mapping) - } - - err = s.Update(ctx, tt.args.mapping) - if (err != nil) != (tt.wants.err != nil) { - t.Errorf("MappingsStore.Update() error = %v, want error %v", err, tt.wants.err) - return - } - if diff := cmp.Diff(tt.args.mapping, tt.wants.mapping, mappingCmpOptions...); diff != "" { - t.Errorf("MappingStore.Update():\n-got/+want\ndiff %s", diff) - return - } - }) - } -} diff --git a/chronograf/bolt/org_config.go b/chronograf/bolt/org_config.go deleted file mode 100644 index 855324c54d6..00000000000 --- a/chronograf/bolt/org_config.go +++ /dev/null @@ -1,236 +0,0 @@ -package bolt - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - bolt "go.etcd.io/bbolt" -) - -// Ensure OrganizationConfigStore implements chronograf.OrganizationConfigStore. -var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{} - -// OrganizationConfigBucket is used to store chronograf organization configurations -var OrganizationConfigBucket = []byte("OrganizationConfigV1") - -// OrganizationConfigStore uses bolt to store and retrieve organization configurations -type OrganizationConfigStore struct { - client *Client -} - -func (s *OrganizationConfigStore) Migrate(ctx context.Context) error { - return nil -} - -// Get retrieves an OrganizationConfig from the store -func (s *OrganizationConfigStore) Get(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - var c chronograf.OrganizationConfig - - err := s.client.db.View(func(tx *bolt.Tx) error { - return s.get(ctx, tx, orgID, &c) - }) - - if err != nil { - return nil, err - } - - return &c, nil -} - -func (s *OrganizationConfigStore) get(ctx context.Context, tx *bolt.Tx, orgID string, c *chronograf.OrganizationConfig) error { - v := tx.Bucket(OrganizationConfigBucket).Get([]byte(orgID)) - if len(v) == 0 { - return chronograf.ErrOrganizationConfigNotFound - } - return internal.UnmarshalOrganizationConfig(v, c) -} - -// FindOrCreate gets an OrganizationConfig from the store or creates one if none exists for this organization -func (s *OrganizationConfigStore) FindOrCreate(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - var c chronograf.OrganizationConfig - err := s.client.db.Update(func(tx *bolt.Tx) error { - err := s.get(ctx, tx, orgID, &c) - if err == chronograf.ErrOrganizationConfigNotFound { - c = newOrganizationConfig(orgID) - return s.put(ctx, tx, &c) - } - return err - }) - - if err != nil { - return nil, err - } - return &c, nil -} - -// Put replaces the OrganizationConfig in the store -func (s *OrganizationConfigStore) Put(ctx context.Context, c *chronograf.OrganizationConfig) error { - return s.client.db.Update(func(tx *bolt.Tx) error { - return s.put(ctx, tx, c) - }) -} - -func (s *OrganizationConfigStore) put(ctx context.Context, tx *bolt.Tx, c *chronograf.OrganizationConfig) error { - if c == nil { - return fmt.Errorf("config provided was nil") - } - if v, err := internal.MarshalOrganizationConfig(c); err != nil { - return err - } else if err := tx.Bucket(OrganizationConfigBucket).Put([]byte(c.OrganizationID), v); err != nil { - return err - } - return nil -} - -func newOrganizationConfig(orgID string) chronograf.OrganizationConfig { - return chronograf.OrganizationConfig{ - OrganizationID: orgID, - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - { - Name: "severity", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "icon", - }, - { - Type: "label", - Value: "text", - }, - { - Type: "color", - Name: "emerg", - Value: "ruby", - }, - { - Type: "color", - Name: "alert", - Value: "fire", - }, - { - Type: "color", - Name: "crit", - Value: "curacao", - }, - { - Type: "color", - Name: "err", - Value: "tiger", - }, - { - Type: "color", - Name: "warning", - Value: "pineapple", - }, - { - Type: "color", - Name: "notice", - Value: "rainforest", - }, - { - Type: "color", - Name: "info", - Value: "star", - }, - { - Type: "color", - Name: "debug", - Value: "wolf", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Proc ID", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - } -} diff --git a/chronograf/bolt/org_config_test.go b/chronograf/bolt/org_config_test.go deleted file mode 100644 index 3c4522b16df..00000000000 --- a/chronograf/bolt/org_config_test.go +++ /dev/null @@ -1,1160 +0,0 @@ -package bolt_test - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestOrganizationConfig_FindOrCreate(t *testing.T) { - type args struct { - organizationID string - } - type wants struct { - organizationConfig *chronograf.OrganizationConfig - err error - } - tests := []struct { - name string - args args - addFirst bool - wants wants - }{ - { - name: "Get non-existent default config from default org", - args: args{ - organizationID: "default", - }, - addFirst: false, - wants: wants{ - organizationConfig: &chronograf.OrganizationConfig{ - OrganizationID: "default", - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - { - Name: "severity", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "icon", - }, - { - Type: "label", - Value: "text", - }, - { - Type: "color", - Name: "emerg", - Value: "ruby", - }, - { - Type: "color", - Name: "alert", - Value: "fire", - }, - { - Type: "color", - Name: "crit", - Value: "curacao", - }, - { - Type: "color", - Name: "err", - Value: "tiger", - }, - { - Type: "color", - Name: "warning", - Value: "pineapple", - }, - { - Type: "color", - Name: "notice", - Value: "rainforest", - }, - { - Type: "color", - Name: "info", - Value: "star", - }, - { - Type: "color", - Name: "debug", - Value: "wolf", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Proc ID", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "Get non-existent default config from non-default org", - args: args{ - organizationID: "1", - }, - addFirst: false, - wants: wants{ - organizationConfig: &chronograf.OrganizationConfig{ - OrganizationID: "1", - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - { - Name: "severity", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "icon", - }, - { - Type: "label", - Value: "text", - }, - { - Type: "color", - Name: "emerg", - Value: "ruby", - }, - { - Type: "color", - Name: "alert", - Value: "fire", - }, - { - Type: "color", - Name: "crit", - Value: "curacao", - }, - { - Type: "color", - Name: "err", - Value: "tiger", - }, - { - Type: "color", - Name: "warning", - Value: "pineapple", - }, - { - Type: "color", - Name: "notice", - Value: "rainforest", - }, - { - Type: "color", - Name: "info", - Value: "star", - }, - { - Type: "color", - Name: "debug", - Value: "wolf", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Proc ID", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "Get existing/modified config from default org", - args: args{ - organizationID: "default", - }, - addFirst: true, - wants: wants{ - organizationConfig: &chronograf.OrganizationConfig{ - OrganizationID: "default", - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "hidden", - }, - { - Type: "label", - Value: "icon", - }, - { - Type: "label", - Value: "text", - }, - { - Type: "color", - Name: "emerg", - Value: "ruby", - }, - { - Type: "color", - Name: "alert", - Value: "fire", - }, - { - Type: "color", - Name: "crit", - Value: "curacao", - }, - { - Type: "color", - Name: "err", - Value: "tiger", - }, - { - Type: "color", - Name: "warning", - Value: "pineapple", - }, - { - Type: "color", - Name: "notice", - Value: "rainforest", - }, - { - Type: "color", - Name: "info", - Value: "star", - }, - { - Type: "color", - Name: "debug", - Value: "wolf", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Proc ID", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "Get existing/modified config from non-default org", - args: args{ - organizationID: "1", - }, - addFirst: true, - wants: wants{ - organizationConfig: &chronograf.OrganizationConfig{ - OrganizationID: "1", - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "hidden", - }, - { - Type: "label", - Value: "icon", - }, - { - Type: "label", - Value: "text", - }, - { - Type: "color", - Name: "emerg", - Value: "ruby", - }, - { - Type: "color", - Name: "alert", - Value: "fire", - }, - { - Type: "color", - Name: "crit", - Value: "curacao", - }, - { - Type: "color", - Name: "err", - Value: "tiger", - }, - { - Type: "color", - Name: "warning", - Value: "pineapple", - }, - { - Type: "color", - Name: "notice", - Value: "rainforest", - }, - { - Type: "color", - Name: "info", - Value: "star", - }, - { - Type: "color", - Name: "debug", - Value: "wolf", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Proc ID", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.OrganizationConfigStore - - if tt.addFirst { - if err := s.Put(context.Background(), tt.wants.organizationConfig); err != nil { - t.Fatal(err) - } - } - - got, err := s.FindOrCreate(context.Background(), tt.args.organizationID) - - if (tt.wants.err != nil) != (err != nil) { - t.Errorf("%q. OrganizationConfigStore.FindOrCreate() error = %v, wantErr %v", tt.name, err, tt.wants.err) - continue - } - if diff := cmp.Diff(got, tt.wants.organizationConfig); diff != "" { - t.Errorf("%q. OrganizationConfigStore.FindOrCreate():\n-got/+want\ndiff %s", tt.name, diff) - } - - d, err := s.Get(context.Background(), tt.args.organizationID) - if err != nil { - t.Errorf("%q. OrganizationConfigStore.Get(): Failed to retrieve organization config", tt.name) - } - if diff := cmp.Diff(got, d); diff != "" { - t.Errorf("%q. OrganizationConfigStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestOrganizationConfig_Put(t *testing.T) { - type args struct { - organizationConfig *chronograf.OrganizationConfig - organizationID string - } - type wants struct { - organizationConfig *chronograf.OrganizationConfig - err error - } - tests := []struct { - name string - args args - wants wants - }{ - { - name: "Set default org config", - args: args{ - organizationConfig: &chronograf.OrganizationConfig{ - OrganizationID: "default", - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "text", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Milkshake", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - organizationID: "default", - }, - wants: wants{ - organizationConfig: &chronograf.OrganizationConfig{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "text", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Milkshake", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - OrganizationID: "default", - }, - }, - }, - { - name: "Set non-default org config", - args: args{ - organizationConfig: &chronograf.OrganizationConfig{ - OrganizationID: "1337", - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "text", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Milkshake", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - organizationID: "1337", - }, - wants: wants{ - organizationConfig: &chronograf.OrganizationConfig{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "text", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Milkshake", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - OrganizationID: "1337", - }, - }, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.OrganizationConfigStore - err = s.Put(context.Background(), tt.args.organizationConfig) - if (tt.wants.err != nil) != (err != nil) { - t.Errorf("%q. OrganizationConfigStore.Put() error = %v, wantErr %v", tt.name, err, tt.wants.err) - continue - } - - got, _ := s.FindOrCreate(context.Background(), tt.args.organizationID) - if (tt.wants.err != nil) != (err != nil) { - t.Errorf("%q. OrganizationConfigStore.Put() error = %v, wantErr %v", tt.name, err, tt.wants.err) - continue - } - - if diff := cmp.Diff(got, tt.wants.organizationConfig); diff != "" { - t.Errorf("%q. OrganizationConfigStore.Put():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} diff --git a/chronograf/bolt/organizations.go b/chronograf/bolt/organizations.go deleted file mode 100644 index f3e0f687bd9..00000000000 --- a/chronograf/bolt/organizations.go +++ /dev/null @@ -1,304 +0,0 @@ -package bolt - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - "github.com/influxdata/influxdb/v2/chronograf/organizations" - bolt "go.etcd.io/bbolt" -) - -// Ensure OrganizationsStore implements chronograf.OrganizationsStore. -var _ chronograf.OrganizationsStore = &OrganizationsStore{} - -var ( - // OrganizationsBucket is the bucket where organizations are stored. - OrganizationsBucket = []byte("OrganizationsV1") - // DefaultOrganizationID is the ID of the default organization. - DefaultOrganizationID = []byte("default") -) - -const ( - // DefaultOrganizationName is the Name of the default organization - DefaultOrganizationName string = "Default" - // DefaultOrganizationRole is the DefaultRole for the Default organization - DefaultOrganizationRole string = "member" -) - -// OrganizationsStore uses bolt to store and retrieve Organizations -type OrganizationsStore struct { - client *Client -} - -// Migrate sets the default organization at runtime -func (s *OrganizationsStore) Migrate(ctx context.Context) error { - return s.CreateDefault(ctx) -} - -// CreateDefault does a findOrCreate on the default organization -func (s *OrganizationsStore) CreateDefault(ctx context.Context) error { - o := chronograf.Organization{ - ID: string(DefaultOrganizationID), - Name: DefaultOrganizationName, - DefaultRole: DefaultOrganizationRole, - } - - m := chronograf.Mapping{ - ID: string(DefaultOrganizationID), - Organization: string(DefaultOrganizationID), - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - } - return s.client.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(OrganizationsBucket) - v := b.Get(DefaultOrganizationID) - if v != nil { - return nil - } - if v, err := internal.MarshalOrganization(&o); err != nil { - return err - } else if err := b.Put(DefaultOrganizationID, v); err != nil { - return err - } - - b = tx.Bucket(MappingsBucket) - v = b.Get(DefaultOrganizationID) - if v != nil { - return nil - } - if v, err := internal.MarshalMapping(&m); err != nil { - return err - } else if err := b.Put(DefaultOrganizationID, v); err != nil { - return err - } - - return nil - }) -} - -func (s *OrganizationsStore) nameIsUnique(ctx context.Context, name string) bool { - _, err := s.Get(ctx, chronograf.OrganizationQuery{Name: &name}) - switch err { - case chronograf.ErrOrganizationNotFound: - return true - default: - return false - } -} - -// DefaultOrganizationID returns the ID of the default organization -func (s *OrganizationsStore) DefaultOrganization(ctx context.Context) (*chronograf.Organization, error) { - var org chronograf.Organization - if err := s.client.db.View(func(tx *bolt.Tx) error { - v := tx.Bucket(OrganizationsBucket).Get(DefaultOrganizationID) - return internal.UnmarshalOrganization(v, &org) - }); err != nil { - return nil, err - } - - return &org, nil -} - -// Add creates a new Organization in the OrganizationsStore -func (s *OrganizationsStore) Add(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { - if !s.nameIsUnique(ctx, o.Name) { - return nil, chronograf.ErrOrganizationAlreadyExists - } - err := s.client.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(OrganizationsBucket) - seq, err := b.NextSequence() - if err != nil { - return err - } - o.ID = fmt.Sprintf("%d", seq) - - v, err := internal.MarshalOrganization(o) - if err != nil { - return err - } - - return b.Put([]byte(o.ID), v) - }) - - return o, err -} - -// All returns all known organizations -func (s *OrganizationsStore) All(ctx context.Context) ([]chronograf.Organization, error) { - var orgs []chronograf.Organization - err := s.each(ctx, func(o *chronograf.Organization) { - orgs = append(orgs, *o) - }) - - if err != nil { - return nil, err - } - - return orgs, nil -} - -// Delete the organization from OrganizationsStore -func (s *OrganizationsStore) Delete(ctx context.Context, o *chronograf.Organization) error { - if o.ID == string(DefaultOrganizationID) { - return chronograf.ErrCannotDeleteDefaultOrganization - } - _, err := s.get(ctx, o.ID) - if err != nil { - return err - } - if err := s.client.db.Update(func(tx *bolt.Tx) error { - return tx.Bucket(OrganizationsBucket).Delete([]byte(o.ID)) - }); err != nil { - return err - } - - // Dependent Delete of all resources - - // Each of the associated organization stores expects organization to be - // set on the context. - ctx = context.WithValue(ctx, organizations.ContextKey, o.ID) - - sourcesStore := organizations.NewSourcesStore(s.client.SourcesStore, o.ID) - sources, err := sourcesStore.All(ctx) - if err != nil { - return err - } - for _, source := range sources { - if err := sourcesStore.Delete(ctx, source); err != nil { - return err - } - } - - serversStore := organizations.NewServersStore(s.client.ServersStore, o.ID) - servers, err := serversStore.All(ctx) - if err != nil { - return err - } - for _, server := range servers { - if err := serversStore.Delete(ctx, server); err != nil { - return err - } - } - - dashboardsStore := organizations.NewDashboardsStore(s.client.DashboardsStore, o.ID) - dashboards, err := dashboardsStore.All(ctx) - if err != nil { - return err - } - for _, dashboard := range dashboards { - if err := dashboardsStore.Delete(ctx, dashboard); err != nil { - return err - } - } - - usersStore := organizations.NewUsersStore(s.client.UsersStore, o.ID) - users, err := usersStore.All(ctx) - if err != nil { - return err - } - for _, user := range users { - if err := usersStore.Delete(ctx, &user); err != nil { - return err - } - } - - mappings, err := s.client.MappingsStore.All(ctx) - if err != nil { - return err - } - for _, mapping := range mappings { - if mapping.Organization == o.ID { - if err := s.client.MappingsStore.Delete(ctx, &mapping); err != nil { - return err - } - } - } - - return nil -} - -func (s *OrganizationsStore) get(ctx context.Context, id string) (*chronograf.Organization, error) { - var o chronograf.Organization - err := s.client.db.View(func(tx *bolt.Tx) error { - v := tx.Bucket(OrganizationsBucket).Get([]byte(id)) - if v == nil { - return chronograf.ErrOrganizationNotFound - } - return internal.UnmarshalOrganization(v, &o) - }) - - if err != nil { - return nil, err - } - - return &o, nil -} - -func (s *OrganizationsStore) each(ctx context.Context, fn func(*chronograf.Organization)) error { - return s.client.db.View(func(tx *bolt.Tx) error { - return tx.Bucket(OrganizationsBucket).ForEach(func(k, v []byte) error { - var org chronograf.Organization - if err := internal.UnmarshalOrganization(v, &org); err != nil { - return err - } - fn(&org) - return nil - }) - }) -} - -// Get returns a Organization if the id exists. -// If an ID is provided in the query, the lookup time for an organization will be O(1). -// If Name is provided, the lookup time will be O(n). -// Get expects that only one of ID or Name will be specified, but will prefer ID over Name if both are specified. -func (s *OrganizationsStore) Get(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID != nil { - return s.get(ctx, *q.ID) - } - - if q.Name != nil { - var org *chronograf.Organization - err := s.each(ctx, func(o *chronograf.Organization) { - if org != nil { - return - } - - if o.Name == *q.Name { - org = o - } - }) - - if err != nil { - return nil, err - } - - if org == nil { - return nil, chronograf.ErrOrganizationNotFound - } - - return org, nil - } - return nil, fmt.Errorf("must specify either ID, or Name in OrganizationQuery") -} - -// Update the organization in OrganizationsStore -func (s *OrganizationsStore) Update(ctx context.Context, o *chronograf.Organization) error { - org, err := s.get(ctx, o.ID) - if err != nil { - return err - } - if o.Name != org.Name && !s.nameIsUnique(ctx, o.Name) { - return chronograf.ErrOrganizationAlreadyExists - } - return s.client.db.Update(func(tx *bolt.Tx) error { - if v, err := internal.MarshalOrganization(o); err != nil { - return err - } else if err := tx.Bucket(OrganizationsBucket).Put([]byte(o.ID), v); err != nil { - return err - } - return nil - }) -} diff --git a/chronograf/bolt/organizations_test.go b/chronograf/bolt/organizations_test.go deleted file mode 100644 index 827fe213056..00000000000 --- a/chronograf/bolt/organizations_test.go +++ /dev/null @@ -1,659 +0,0 @@ -package bolt_test - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -var orgCmpOptions = cmp.Options{ - cmpopts.IgnoreFields(chronograf.Organization{}, "ID"), - cmpopts.EquateEmpty(), -} - -func TestOrganizationsStore_GetWithName(t *testing.T) { - type args struct { - ctx context.Context - org *chronograf.Organization - } - tests := []struct { - name string - args args - want *chronograf.Organization - wantErr bool - addFirst bool - }{ - { - name: "Organization not found", - args: args{ - ctx: context.Background(), - org: &chronograf.Organization{}, - }, - wantErr: true, - }, - { - name: "Get Organization", - args: args{ - ctx: context.Background(), - org: &chronograf.Organization{ - Name: "EE - Evil Empire", - }, - }, - want: &chronograf.Organization{ - Name: "EE - Evil Empire", - }, - addFirst: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.OrganizationsStore - if tt.addFirst { - tt.args.org, err = s.Add(tt.args.ctx, tt.args.org) - if err != nil { - t.Fatal(err) - } - } - - got, err := s.Get(tt.args.ctx, chronograf.OrganizationQuery{Name: &tt.args.org.Name}) - if (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - if tt.wantErr { - return - } - if diff := cmp.Diff(got, tt.want, orgCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - - }) - } -} - -func TestOrganizationsStore_GetWithID(t *testing.T) { - type args struct { - ctx context.Context - org *chronograf.Organization - } - tests := []struct { - name string - args args - want *chronograf.Organization - wantErr bool - addFirst bool - }{ - { - name: "Organization not found", - args: args{ - ctx: context.Background(), - org: &chronograf.Organization{ - ID: "1234", - }, - }, - wantErr: true, - }, - { - name: "Get Organization", - args: args{ - ctx: context.Background(), - org: &chronograf.Organization{ - Name: "EE - Evil Empire", - }, - }, - want: &chronograf.Organization{ - Name: "EE - Evil Empire", - }, - addFirst: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.OrganizationsStore - if tt.addFirst { - tt.args.org, err = s.Add(tt.args.ctx, tt.args.org) - if err != nil { - t.Fatal(err) - } - } - - got, err := s.Get(tt.args.ctx, chronograf.OrganizationQuery{ID: &tt.args.org.ID}) - if (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - return - } - if tt.wantErr { - return - } - if diff := cmp.Diff(got, tt.want, orgCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - - }) - } -} - -func TestOrganizationsStore_All(t *testing.T) { - type args struct { - ctx context.Context - orgs []chronograf.Organization - } - tests := []struct { - name string - args args - want []chronograf.Organization - addFirst bool - }{ - { - name: "Get Organizations", - args: args{ - ctx: context.Background(), - orgs: []chronograf.Organization{ - { - Name: "EE - Evil Empire", - DefaultRole: roles.MemberRoleName, - }, - { - Name: "The Good Place", - DefaultRole: roles.EditorRoleName, - }, - }, - }, - want: []chronograf.Organization{ - { - Name: "EE - Evil Empire", - DefaultRole: roles.MemberRoleName, - }, - { - Name: "The Good Place", - DefaultRole: roles.EditorRoleName, - }, - { - Name: bolt.DefaultOrganizationName, - DefaultRole: bolt.DefaultOrganizationRole, - }, - }, - addFirst: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.OrganizationsStore - if tt.addFirst { - for _, org := range tt.args.orgs { - _, err = s.Add(tt.args.ctx, &org) - if err != nil { - t.Fatal(err) - } - } - } - - got, err := s.All(tt.args.ctx) - if err != nil { - t.Fatal(err) - return - } - if diff := cmp.Diff(got, tt.want, orgCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.All():\n-got/+want\ndiff %s", tt.name, diff) - } - }) - } -} - -func TestOrganizationsStore_Update(t *testing.T) { - type fields struct { - orgs []chronograf.Organization - } - type args struct { - ctx context.Context - initial *chronograf.Organization - updates *chronograf.Organization - } - tests := []struct { - name string - fields fields - args args - addFirst bool - want *chronograf.Organization - wantErr bool - }{ - { - name: "No such organization", - fields: fields{}, - args: args{ - ctx: context.Background(), - initial: &chronograf.Organization{ - ID: "1234", - Name: "The Okay Place", - }, - updates: &chronograf.Organization{}, - }, - wantErr: true, - }, - { - name: "Update organization name", - fields: fields{}, - args: args{ - ctx: context.Background(), - initial: &chronograf.Organization{ - Name: "The Good Place", - }, - updates: &chronograf.Organization{ - Name: "The Bad Place", - }, - }, - want: &chronograf.Organization{ - Name: "The Bad Place", - }, - addFirst: true, - }, - { - name: "Update organization default role", - fields: fields{}, - args: args{ - ctx: context.Background(), - initial: &chronograf.Organization{ - Name: "The Good Place", - }, - updates: &chronograf.Organization{ - DefaultRole: roles.ViewerRoleName, - }, - }, - want: &chronograf.Organization{ - Name: "The Good Place", - DefaultRole: roles.ViewerRoleName, - }, - addFirst: true, - }, - { - name: "Update organization name and default role", - fields: fields{}, - args: args{ - ctx: context.Background(), - initial: &chronograf.Organization{ - Name: "The Good Place", - DefaultRole: roles.AdminRoleName, - }, - updates: &chronograf.Organization{ - Name: "The Bad Place", - DefaultRole: roles.ViewerRoleName, - }, - }, - want: &chronograf.Organization{ - Name: "The Bad Place", - DefaultRole: roles.ViewerRoleName, - }, - addFirst: true, - }, - { - name: "Update organization name, role", - fields: fields{}, - args: args{ - ctx: context.Background(), - initial: &chronograf.Organization{ - Name: "The Good Place", - DefaultRole: roles.ViewerRoleName, - }, - updates: &chronograf.Organization{ - Name: "The Bad Place", - DefaultRole: roles.AdminRoleName, - }, - }, - want: &chronograf.Organization{ - Name: "The Bad Place", - DefaultRole: roles.AdminRoleName, - }, - addFirst: true, - }, - { - name: "Update organization name", - fields: fields{}, - args: args{ - ctx: context.Background(), - initial: &chronograf.Organization{ - Name: "The Good Place", - DefaultRole: roles.EditorRoleName, - }, - updates: &chronograf.Organization{ - Name: "The Bad Place", - }, - }, - want: &chronograf.Organization{ - Name: "The Bad Place", - DefaultRole: roles.EditorRoleName, - }, - addFirst: true, - }, - { - name: "Update organization name", - fields: fields{}, - args: args{ - ctx: context.Background(), - initial: &chronograf.Organization{ - Name: "The Good Place", - }, - updates: &chronograf.Organization{ - Name: "The Bad Place", - }, - }, - want: &chronograf.Organization{ - Name: "The Bad Place", - }, - addFirst: true, - }, - { - name: "Update organization name - name already taken", - fields: fields{ - orgs: []chronograf.Organization{ - { - Name: "The Bad Place", - }, - }, - }, - args: args{ - ctx: context.Background(), - initial: &chronograf.Organization{ - Name: "The Good Place", - }, - updates: &chronograf.Organization{ - Name: "The Bad Place", - }, - }, - wantErr: true, - addFirst: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.OrganizationsStore - - for _, org := range tt.fields.orgs { - _, err = s.Add(tt.args.ctx, &org) - if err != nil { - t.Fatal(err) - } - } - - if tt.addFirst { - tt.args.initial, err = s.Add(tt.args.ctx, tt.args.initial) - if err != nil { - t.Fatal(err) - } - } - - if tt.args.updates.Name != "" { - tt.args.initial.Name = tt.args.updates.Name - } - if tt.args.updates.DefaultRole != "" { - tt.args.initial.DefaultRole = tt.args.updates.DefaultRole - } - - if err := s.Update(tt.args.ctx, tt.args.initial); (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - - // for the empty test - if tt.want == nil { - continue - } - - got, err := s.Get(tt.args.ctx, chronograf.OrganizationQuery{Name: &tt.args.initial.Name}) - if err != nil { - t.Fatalf("failed to get organization: %v", err) - } - if diff := cmp.Diff(got, tt.want, orgCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.Update():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestOrganizationStore_Delete(t *testing.T) { - type args struct { - ctx context.Context - org *chronograf.Organization - } - tests := []struct { - name string - args args - addFirst bool - wantErr bool - }{ - { - name: "No such organization", - args: args{ - ctx: context.Background(), - org: &chronograf.Organization{ - ID: "10", - }, - }, - wantErr: true, - }, - { - name: "Delete new organization", - args: args{ - ctx: context.Background(), - org: &chronograf.Organization{ - Name: "The Deleted Place", - }, - }, - addFirst: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.OrganizationsStore - - if tt.addFirst { - tt.args.org, _ = s.Add(tt.args.ctx, tt.args.org) - } - if err := s.Delete(tt.args.ctx, tt.args.org); (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Delete() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - } -} - -func TestOrganizationStore_DeleteDefaultOrg(t *testing.T) { - type args struct { - ctx context.Context - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Delete the default organization", - args: args{ - ctx: context.Background(), - }, - wantErr: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.OrganizationsStore - - defaultOrg, err := s.DefaultOrganization(tt.args.ctx) - if err != nil { - t.Fatal(err) - } - if err := s.Delete(tt.args.ctx, defaultOrg); (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Delete() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - } -} - -func TestOrganizationsStore_Add(t *testing.T) { - type fields struct { - orgs []chronograf.Organization - } - type args struct { - ctx context.Context - org *chronograf.Organization - } - tests := []struct { - name string - fields fields - args args - want *chronograf.Organization - wantErr bool - }{ - { - name: "Add organization - organization already exists", - fields: fields{ - orgs: []chronograf.Organization{ - { - Name: "The Good Place", - }, - }, - }, - args: args{ - ctx: context.Background(), - org: &chronograf.Organization{ - Name: "The Good Place", - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.OrganizationsStore - - for _, org := range tt.fields.orgs { - _, err = s.Add(tt.args.ctx, &org) - if err != nil { - t.Fatal(err) - } - } - - _, err = s.Add(tt.args.ctx, tt.args.org) - - if (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - - // for the empty test - if tt.want == nil { - continue - } - - got, err := s.Get(tt.args.ctx, chronograf.OrganizationQuery{Name: &tt.args.org.Name}) - if err != nil { - t.Fatalf("failed to get organization: %v", err) - } - if diff := cmp.Diff(got, tt.want, orgCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.Update():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestOrganizationsStore_DefaultOrganization(t *testing.T) { - type fields struct { - orgs []chronograf.Organization - } - type args struct { - ctx context.Context - } - tests := []struct { - name string - fields fields - args args - want *chronograf.Organization - wantErr bool - }{ - { - name: "Get Default Organization", - fields: fields{ - orgs: []chronograf.Organization{ - { - Name: "The Good Place", - }, - }, - }, - args: args{ - ctx: context.Background(), - }, - want: &chronograf.Organization{ - ID: string(bolt.DefaultOrganizationID), - Name: bolt.DefaultOrganizationName, - DefaultRole: bolt.DefaultOrganizationRole, - }, - wantErr: false, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - s := client.OrganizationsStore - - for _, org := range tt.fields.orgs { - _, err = s.Add(tt.args.ctx, &org) - if err != nil { - t.Fatal(err) - } - } - - got, err := s.DefaultOrganization(tt.args.ctx) - - if (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - - if tt.want == nil { - continue - } - - if diff := cmp.Diff(got, tt.want, orgCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.Update():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} diff --git a/chronograf/bolt/servers.go b/chronograf/bolt/servers.go deleted file mode 100644 index dbe694f309f..00000000000 --- a/chronograf/bolt/servers.go +++ /dev/null @@ -1,183 +0,0 @@ -package bolt - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - bolt "go.etcd.io/bbolt" -) - -// Ensure ServersStore implements chronograf.ServersStore. -var _ chronograf.ServersStore = &ServersStore{} - -// ServersBucket is the bolt bucket to store lists of servers -var ServersBucket = []byte("Servers") - -// ServersStore is the bolt implementation to store servers in a store. -// Used store servers that are associated in some way with a source -type ServersStore struct { - client *Client -} - -func (s *ServersStore) Migrate(ctx context.Context) error { - servers, err := s.All(ctx) - if err != nil { - return err - } - - defaultOrg, err := s.client.OrganizationsStore.DefaultOrganization(ctx) - if err != nil { - return err - } - - for _, server := range servers { - if server.Organization == "" { - server.Organization = defaultOrg.ID - if err := s.Update(ctx, server); err != nil { - return nil - } - } - } - - return nil -} - -// All returns all known servers -func (s *ServersStore) All(ctx context.Context) ([]chronograf.Server, error) { - var srcs []chronograf.Server - if err := s.client.db.View(func(tx *bolt.Tx) error { - var err error - srcs, err = s.all(ctx, tx) - if err != nil { - return err - } - return nil - }); err != nil { - return nil, err - } - - return srcs, nil - -} - -// Add creates a new Server in the ServerStore. -func (s *ServersStore) Add(ctx context.Context, src chronograf.Server) (chronograf.Server, error) { - if err := s.client.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(ServersBucket) - seq, err := b.NextSequence() - if err != nil { - return err - } - src.ID = int(seq) - - // make the newly added source "active" - s.resetActiveServer(ctx, tx) - src.Active = true - - if v, err := internal.MarshalServer(src); err != nil { - return err - } else if err := b.Put(itob(src.ID), v); err != nil { - return err - } - return nil - }); err != nil { - return chronograf.Server{}, err - } - - return src, nil -} - -// Delete removes the Server from the ServersStore -func (s *ServersStore) Delete(ctx context.Context, src chronograf.Server) error { - if err := s.client.db.Update(func(tx *bolt.Tx) error { - if err := tx.Bucket(ServersBucket).Delete(itob(src.ID)); err != nil { - return err - } - return nil - }); err != nil { - return err - } - - return nil -} - -// Get returns a Server if the id exists. -func (s *ServersStore) Get(ctx context.Context, id int) (chronograf.Server, error) { - var src chronograf.Server - if err := s.client.db.View(func(tx *bolt.Tx) error { - if v := tx.Bucket(ServersBucket).Get(itob(id)); v == nil { - return chronograf.ErrServerNotFound - } else if err := internal.UnmarshalServer(v, &src); err != nil { - return err - } - return nil - }); err != nil { - return chronograf.Server{}, err - } - - return src, nil -} - -// Update a Server -func (s *ServersStore) Update(ctx context.Context, src chronograf.Server) error { - if err := s.client.db.Update(func(tx *bolt.Tx) error { - // Get an existing server with the same ID. - b := tx.Bucket(ServersBucket) - if v := b.Get(itob(src.ID)); v == nil { - return chronograf.ErrServerNotFound - } - - // only one server can be active at a time - if src.Active { - s.resetActiveServer(ctx, tx) - } - - if v, err := internal.MarshalServer(src); err != nil { - return err - } else if err := b.Put(itob(src.ID), v); err != nil { - return err - } - return nil - }); err != nil { - return err - } - - return nil -} - -func (s *ServersStore) all(ctx context.Context, tx *bolt.Tx) ([]chronograf.Server, error) { - var srcs []chronograf.Server - if err := tx.Bucket(ServersBucket).ForEach(func(k, v []byte) error { - var src chronograf.Server - if err := internal.UnmarshalServer(v, &src); err != nil { - return err - } - srcs = append(srcs, src) - return nil - }); err != nil { - return srcs, err - } - return srcs, nil -} - -// resetActiveServer unsets the Active flag on all sources -func (s *ServersStore) resetActiveServer(ctx context.Context, tx *bolt.Tx) error { - b := tx.Bucket(ServersBucket) - srcs, err := s.all(ctx, tx) - if err != nil { - return err - } - - for _, other := range srcs { - if other.Active { - other.Active = false - if v, err := internal.MarshalServer(other); err != nil { - return err - } else if err := b.Put(itob(other.ID), v); err != nil { - return err - } - } - } - return nil -} diff --git a/chronograf/bolt/servers_test.go b/chronograf/bolt/servers_test.go deleted file mode 100644 index dbf6f72cb1f..00000000000 --- a/chronograf/bolt/servers_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package bolt_test - -import ( - "context" - "reflect" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Ensure an ServerStore can store, retrieve, update, and delete servers. -func TestServerStore(t *testing.T) { - c, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer c.Close() - - s := c.ServersStore - - srcs := []chronograf.Server{ - chronograf.Server{ - Name: "Of Truth", - SrcID: 10, - Username: "marty", - Password: "I❤️ jennifer parker", - URL: "toyota-hilux.lyon-estates.local", - Active: false, - Organization: "133", - InsecureSkipVerify: true, - }, - chronograf.Server{ - Name: "HipToBeSquare", - SrcID: 12, - Username: "calvinklein", - Password: "chuck b3rry", - URL: "toyota-hilux.lyon-estates.local", - Active: false, - Organization: "133", - InsecureSkipVerify: false, - }, - } - - // Add new srcs. - ctx := context.Background() - for i, src := range srcs { - if srcs[i], err = s.Add(ctx, src); err != nil { - t.Fatal(err) - } - // Confirm first src in the store is the same as the original. - if actual, err := s.Get(ctx, srcs[i].ID); err != nil { - t.Fatal(err) - } else if !reflect.DeepEqual(actual, srcs[i]) { - t.Fatalf("server loaded is different then server saved; actual: %v, expected %v", actual, srcs[i]) - } - } - - // Update server. - srcs[0].Username = "calvinklein" - srcs[1].Name = "Enchantment Under the Sea Dance" - srcs[1].Organization = "1234" - if err := s.Update(ctx, srcs[0]); err != nil { - t.Fatal(err) - } else if err := s.Update(ctx, srcs[1]); err != nil { - t.Fatal(err) - } - - // Confirm servers have updated. - if src, err := s.Get(ctx, srcs[0].ID); err != nil { - t.Fatal(err) - } else if src.Username != "calvinklein" { - t.Fatalf("server 0 update error: got %v, expected %v", src.Username, "calvinklein") - } - if src, err := s.Get(ctx, srcs[1].ID); err != nil { - t.Fatal(err) - } else if src.Name != "Enchantment Under the Sea Dance" { - t.Fatalf("server 1 update error: got %v, expected %v", src.Name, "Enchantment Under the Sea Dance") - } else if src.Organization != "1234" { - t.Fatalf("server 1 update error: got %v, expected %v", src.Organization, "1234") - } - - // Attempt to make two active sources - srcs[0].Active = true - srcs[1].Active = true - if err := s.Update(ctx, srcs[0]); err != nil { - t.Fatal(err) - } else if err := s.Update(ctx, srcs[1]); err != nil { - t.Fatal(err) - } - - if actual, err := s.Get(ctx, srcs[0].ID); err != nil { - t.Fatal(err) - } else if actual.Active { - t.Fatal("Able to set two active servers when only one should be permitted") - } - - // Delete an server. - if err := s.Delete(ctx, srcs[0]); err != nil { - t.Fatal(err) - } - - // Confirm server has been deleted. - if _, err := s.Get(ctx, srcs[0].ID); err != chronograf.ErrServerNotFound { - t.Fatalf("server delete error: got %v, expected %v", err, chronograf.ErrServerNotFound) - } - - if bsrcs, err := s.All(ctx); err != nil { - t.Fatal(err) - } else if len(bsrcs) != 1 { - t.Fatalf("After delete All returned incorrect number of srcs; got %d, expected %d", len(bsrcs), 1) - } else if !reflect.DeepEqual(bsrcs[0], srcs[1]) { - t.Fatalf("After delete All returned incorrect server; got %v, expected %v", bsrcs[0], srcs[1]) - } -} diff --git a/chronograf/bolt/sources.go b/chronograf/bolt/sources.go deleted file mode 100644 index 9a608f3ce23..00000000000 --- a/chronograf/bolt/sources.go +++ /dev/null @@ -1,288 +0,0 @@ -package bolt - -import ( - "context" - "math" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - "github.com/influxdata/influxdb/v2/chronograf/roles" - bolt "go.etcd.io/bbolt" -) - -// Ensure SourcesStore implements chronograf.SourcesStore. -var _ chronograf.SourcesStore = &SourcesStore{} - -// SourcesBucket is the bolt bucket used to store source information -var SourcesBucket = []byte("Sources") - -// DefaultSource is a temporary measure for single-binary. -var DefaultSource = &chronograf.Source{ - ID: math.MaxInt32, // Use large number to avoid possible collisions in older chronograf. - Name: "autogen", - Type: "influx", - URL: "http://localhost:8086", - Default: false, -} - -// SourcesStore is a bolt implementation to store time-series source information. -type SourcesStore struct { - client *Client -} - -// Migrate adds the default source to an existing boltdb. -func (s *SourcesStore) Migrate(ctx context.Context) error { - sources, err := s.All(ctx) - if err != nil { - return err - } - if len(sources) == 0 { - if err := s.Put(ctx, DefaultSource); err != nil { - return err - } - } - - defaultOrg, err := s.client.OrganizationsStore.DefaultOrganization(ctx) - if err != nil { - return err - } - - for _, source := range sources { - if source.Organization == "" { - source.Organization = defaultOrg.ID - } - if source.Role == "" { - source.Role = roles.ViewerRoleName - } - if err := s.Update(ctx, source); err != nil { - return nil - } - } - - return nil -} - -// All returns all known sources -func (s *SourcesStore) All(ctx context.Context) ([]chronograf.Source, error) { - var srcs []chronograf.Source - if err := s.client.db.View(func(tx *bolt.Tx) error { - var err error - srcs, err = s.all(ctx, tx) - if err != nil { - return err - } - return nil - }); err != nil { - return nil, err - } - - return srcs, nil - -} - -// Add creates a new Source in the SourceStore. -func (s *SourcesStore) Add(ctx context.Context, src chronograf.Source) (chronograf.Source, error) { - - // force first source added to be default - if srcs, err := s.All(ctx); err != nil { - return chronograf.Source{}, err - } else if len(srcs) == 0 { - src.Default = true - } - - if err := s.client.db.Update(func(tx *bolt.Tx) error { - return s.add(ctx, &src, tx) - }); err != nil { - return chronograf.Source{}, err - } - - return src, nil -} - -// Delete removes the Source from the SourcesStore -func (s *SourcesStore) Delete(ctx context.Context, src chronograf.Source) error { - if err := s.client.db.Update(func(tx *bolt.Tx) error { - if err := s.setRandomDefault(ctx, src, tx); err != nil { - return err - } - return s.delete(ctx, src, tx) - }); err != nil { - return err - } - - return nil -} - -// Get returns a Source if the id exists. -func (s *SourcesStore) Get(ctx context.Context, id int) (chronograf.Source, error) { - var src chronograf.Source - if err := s.client.db.View(func(tx *bolt.Tx) error { - var err error - src, err = s.get(ctx, id, tx) - if err != nil { - return err - } - return nil - }); err != nil { - return chronograf.Source{}, err - } - - return src, nil -} - -// Update a Source -func (s *SourcesStore) Update(ctx context.Context, src chronograf.Source) error { - if err := s.client.db.Update(func(tx *bolt.Tx) error { - return s.update(ctx, src, tx) - }); err != nil { - return err - } - - return nil -} - -func (s *SourcesStore) all(ctx context.Context, tx *bolt.Tx) ([]chronograf.Source, error) { - var srcs []chronograf.Source - if err := tx.Bucket(SourcesBucket).ForEach(func(k, v []byte) error { - var src chronograf.Source - if err := internal.UnmarshalSource(v, &src); err != nil { - return err - } - srcs = append(srcs, src) - return nil - }); err != nil { - return srcs, err - } - return srcs, nil -} - -// Put updates the source. -func (s *SourcesStore) Put(ctx context.Context, src *chronograf.Source) error { - return s.client.db.Update(func(tx *bolt.Tx) error { - return s.put(ctx, src, tx) - }) -} - -func (s *SourcesStore) put(ctx context.Context, src *chronograf.Source, tx *bolt.Tx) error { - b := tx.Bucket(SourcesBucket) - - if v, err := internal.MarshalSource(*src); err != nil { - return err - } else if err := b.Put(itob(src.ID), v); err != nil { - return err - } - return nil -} - -func (s *SourcesStore) add(ctx context.Context, src *chronograf.Source, tx *bolt.Tx) error { - b := tx.Bucket(SourcesBucket) - seq, err := b.NextSequence() - if err != nil { - return err - } - src.ID = int(seq) - - if src.Default { - if err := s.resetDefaultSource(ctx, tx); err != nil { - return err - } - } - - if v, err := internal.MarshalSource(*src); err != nil { - return err - } else if err := b.Put(itob(src.ID), v); err != nil { - return err - } - return nil -} - -func (s *SourcesStore) delete(ctx context.Context, src chronograf.Source, tx *bolt.Tx) error { - if err := tx.Bucket(SourcesBucket).Delete(itob(src.ID)); err != nil { - return err - } - return nil -} - -func (s *SourcesStore) get(ctx context.Context, id int, tx *bolt.Tx) (chronograf.Source, error) { - var src chronograf.Source - if v := tx.Bucket(SourcesBucket).Get(itob(id)); v == nil { - return src, chronograf.ErrSourceNotFound - } else if err := internal.UnmarshalSource(v, &src); err != nil { - return src, err - } - return src, nil -} - -func (s *SourcesStore) update(ctx context.Context, src chronograf.Source, tx *bolt.Tx) error { - // Get an existing source with the same ID. - b := tx.Bucket(SourcesBucket) - if v := b.Get(itob(src.ID)); v == nil { - return chronograf.ErrSourceNotFound - } - - if src.Default { - if err := s.resetDefaultSource(ctx, tx); err != nil { - return err - } - } - - if v, err := internal.MarshalSource(src); err != nil { - return err - } else if err := b.Put(itob(src.ID), v); err != nil { - return err - } - return nil -} - -// resetDefaultSource unsets the Default flag on all sources -func (s *SourcesStore) resetDefaultSource(ctx context.Context, tx *bolt.Tx) error { - b := tx.Bucket(SourcesBucket) - srcs, err := s.all(ctx, tx) - if err != nil { - return err - } - - for _, other := range srcs { - if other.Default { - other.Default = false - if v, err := internal.MarshalSource(other); err != nil { - return err - } else if err := b.Put(itob(other.ID), v); err != nil { - return err - } - } - } - return nil -} - -// setRandomDefault will locate a source other than the provided -// chronograf.Source and set it as the default source. If no other sources are -// available, the provided source will be set to the default source if is not -// already. It assumes that the provided chronograf.Source has been persisted. -func (s *SourcesStore) setRandomDefault(ctx context.Context, src chronograf.Source, tx *bolt.Tx) error { - // Check if requested source is the current default - if target, err := s.get(ctx, src.ID, tx); err != nil { - return err - } else if target.Default { - // Locate another source to be the new default - srcs, err := s.all(ctx, tx) - if err != nil { - return err - } - var other *chronograf.Source - for idx := range srcs { - other = &srcs[idx] - // avoid selecting the source we're about to delete as the new default - if other.ID != target.ID { - break - } - } - - // set the other to be the default - other.Default = true - if err := s.update(ctx, *other, tx); err != nil { - return err - } - } - return nil -} diff --git a/chronograf/bolt/sources_test.go b/chronograf/bolt/sources_test.go deleted file mode 100644 index 9d8b0a652b5..00000000000 --- a/chronograf/bolt/sources_test.go +++ /dev/null @@ -1,200 +0,0 @@ -package bolt_test - -import ( - "context" - "reflect" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt" -) - -// Ensure an SourceStore can store, retrieve, update, and delete sources. -func TestSourceStore(t *testing.T) { - c, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer c.Close() - - s := c.SourcesStore - - srcs := []chronograf.Source{ - chronograf.Source{ - Name: "Of Truth", - Type: "influx", - Username: "marty", - Password: "I❤️ jennifer parker", - URL: "toyota-hilux.lyon-estates.local", - Default: true, - Organization: "1337", - DefaultRP: "pineapple", - }, - chronograf.Source{ - Name: "HipToBeSquare", - Type: "influx", - Username: "calvinklein", - Password: "chuck b3rry", - URL: "toyota-hilux.lyon-estates.local", - Default: true, - Organization: "1337", - }, - chronograf.Source{ - Name: "HipToBeSquare", - Type: "influx", - Username: "calvinklein", - Password: "chuck b3rry", - URL: "https://toyota-hilux.lyon-estates.local", - InsecureSkipVerify: true, - Default: false, - Organization: "1337", - }, - } - - ctx := context.Background() - // Add new srcs. - for i, src := range srcs { - if srcs[i], err = s.Add(ctx, src); err != nil { - t.Fatal(err) - } - // Confirm first src in the store is the same as the original. - if actual, err := s.Get(ctx, srcs[i].ID); err != nil { - t.Fatal(err) - } else if !reflect.DeepEqual(actual, srcs[i]) { - t.Fatalf("source loaded is different then source saved; actual: %v, expected %v", actual, srcs[i]) - } - } - - // Update source. - srcs[0].Username = "calvinklein" - srcs[1].Name = "Enchantment Under the Sea Dance" - srcs[2].DefaultRP = "cubeapple" - mustUpdateSource(t, s, srcs[0]) - mustUpdateSource(t, s, srcs[1]) - mustUpdateSource(t, s, srcs[2]) - - // Confirm sources have updated. - if src, err := s.Get(ctx, srcs[0].ID); err != nil { - t.Fatal(err) - } else if src.Username != "calvinklein" { - t.Fatalf("source 0 update error: got %v, expected %v", src.Username, "calvinklein") - } - if src, err := s.Get(ctx, srcs[1].ID); err != nil { - t.Fatal(err) - } else if src.Name != "Enchantment Under the Sea Dance" { - t.Fatalf("source 1 update error: got %v, expected %v", src.Name, "Enchantment Under the Sea Dance") - } - if src, err := s.Get(ctx, srcs[2].ID); err != nil { - t.Fatal(err) - } else if src.DefaultRP != "cubeapple" { - t.Fatalf("source 2 update error: got %v, expected %v", src.DefaultRP, "cubeapple") - } - - // Attempt to make two default sources - srcs[0].Default = true - srcs[1].Default = true - mustUpdateSource(t, s, srcs[0]) - mustUpdateSource(t, s, srcs[1]) - - if actual, err := s.Get(ctx, srcs[0].ID); err != nil { - t.Fatal(err) - } else if actual.Default { - t.Fatal("Able to set two default sources when only one should be permitted") - } - - // Attempt to add a new default source - srcs = append(srcs, chronograf.Source{ - Name: "Biff Tannen", - Type: "influx", - Username: "HELLO", - Password: "MCFLY", - URL: "anybody.in.there.local", - Default: true, - Organization: "1892", - }) - - srcs[3] = mustAddSource(t, s, srcs[3]) - if srcs, err := s.All(ctx); err != nil { - t.Fatal(err) - } else { - defaults := 0 - for _, src := range srcs { - if src.Default { - defaults++ - } - } - - if defaults != 1 { - t.Fatal("Able to add more than one default source") - } - } - - // Delete an source. - if err := s.Delete(ctx, srcs[0]); err != nil { - t.Fatal(err) - } - - // Confirm source has been deleted. - if _, err := s.Get(ctx, srcs[0].ID); err != chronograf.ErrSourceNotFound { - t.Fatalf("source delete error: got %v, expected %v", err, chronograf.ErrSourceNotFound) - } - - // Delete the other source we created - if err := s.Delete(ctx, srcs[3]); err != nil { - t.Fatal(err) - } - - if bsrcs, err := s.All(ctx); err != nil { - t.Fatal(err) - } else if len(bsrcs) != 3 { - t.Fatalf("After delete All returned incorrect number of srcs; got %d, expected %d", len(bsrcs), 3) - } else if !reflect.DeepEqual(bsrcs[0], srcs[1]) { - t.Fatalf("After delete All returned incorrect source; got %v, expected %v", bsrcs[0], srcs[1]) - } - - // Delete the final sources - if err := s.Delete(ctx, srcs[1]); err != nil { - t.Fatal(err) - } - if err := s.Delete(ctx, srcs[2]); err != nil { - t.Fatal(err) - } - if err := s.Delete(ctx, *bolt.DefaultSource); err != nil { - t.Fatal(err) - } - - // Try to add one source as a non-default and ensure that it becomes a - // default - src := mustAddSource(t, s, chronograf.Source{ - Name: "Biff Tannen", - Type: "influx", - Username: "HELLO", - Password: "MCFLY", - URL: "anybody.in.there.local", - Default: false, - Organization: "1234", - }) - - if actual, err := s.Get(ctx, src.ID); err != nil { - t.Fatal(err) - } else if !actual.Default { - t.Fatal("Expected first source added to be default but wasn't") - } -} - -func mustUpdateSource(t *testing.T, s *bolt.SourcesStore, src chronograf.Source) { - ctx := context.Background() - if err := s.Update(ctx, src); err != nil { - t.Fatal(err) - } -} - -func mustAddSource(t *testing.T, s *bolt.SourcesStore, src chronograf.Source) chronograf.Source { - ctx := context.Background() - if src, err := s.Add(ctx, src); err != nil { - t.Fatal(err) - return src - } else { - return src - } -} diff --git a/chronograf/bolt/users.go b/chronograf/bolt/users.go deleted file mode 100644 index 79fd4997114..00000000000 --- a/chronograf/bolt/users.go +++ /dev/null @@ -1,196 +0,0 @@ -package bolt - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt/internal" - bolt "go.etcd.io/bbolt" -) - -// Ensure UsersStore implements chronograf.UsersStore. -var _ chronograf.UsersStore = &UsersStore{} - -// UsersBucket is used to store users local to chronograf -var UsersBucket = []byte("UsersV2") - -// UsersStore uses bolt to store and retrieve users -type UsersStore struct { - client *Client -} - -// get searches the UsersStore for user with id and returns the bolt representation -func (s *UsersStore) get(ctx context.Context, id uint64) (*chronograf.User, error) { - var u chronograf.User - err := s.client.db.View(func(tx *bolt.Tx) error { - v := tx.Bucket(UsersBucket).Get(u64tob(id)) - if v == nil { - return chronograf.ErrUserNotFound - } - return internal.UnmarshalUser(v, &u) - }) - - if err != nil { - return nil, err - } - - return &u, nil -} - -func (s *UsersStore) each(ctx context.Context, fn func(*chronograf.User)) error { - return s.client.db.View(func(tx *bolt.Tx) error { - return tx.Bucket(UsersBucket).ForEach(func(k, v []byte) error { - var user chronograf.User - if err := internal.UnmarshalUser(v, &user); err != nil { - return err - } - fn(&user) - return nil - }) - }) -} - -// Num returns the number of users in the UsersStore -func (s *UsersStore) Num(ctx context.Context) (int, error) { - count := 0 - - err := s.client.db.View(func(tx *bolt.Tx) error { - return tx.Bucket(UsersBucket).ForEach(func(k, v []byte) error { - count++ - return nil - }) - }) - - if err != nil { - return 0, err - } - - return count, nil -} - -// Get searches the UsersStore for user with name -func (s *UsersStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.ID != nil { - return s.get(ctx, *q.ID) - } - - if q.Name != nil && q.Provider != nil && q.Scheme != nil { - var user *chronograf.User - err := s.each(ctx, func(u *chronograf.User) { - if user != nil { - return - } - if u.Name == *q.Name && u.Provider == *q.Provider && u.Scheme == *q.Scheme { - user = u - } - }) - - if err != nil { - return nil, err - } - - if user == nil { - return nil, chronograf.ErrUserNotFound - } - - return user, nil - } - - return nil, fmt.Errorf("must specify either ID, or Name, Provider, and Scheme in UserQuery") -} - -func (s *UsersStore) userExists(ctx context.Context, u *chronograf.User) (bool, error) { - _, err := s.Get(ctx, chronograf.UserQuery{ - Name: &u.Name, - Provider: &u.Provider, - Scheme: &u.Scheme, - }) - if err == chronograf.ErrUserNotFound { - return false, nil - } - - if err != nil { - return false, err - } - - return true, nil -} - -// Add a new User to the UsersStore. -func (s *UsersStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - if u == nil { - return nil, fmt.Errorf("user provided is nil") - } - userExists, err := s.userExists(ctx, u) - if err != nil { - return nil, err - } - if userExists { - return nil, chronograf.ErrUserAlreadyExists - } - if err := s.client.db.Update(func(tx *bolt.Tx) error { - b := tx.Bucket(UsersBucket) - seq, err := b.NextSequence() - if err != nil { - return err - } - u.ID = seq - if v, err := internal.MarshalUser(u); err != nil { - return err - } else if err := b.Put(u64tob(seq), v); err != nil { - return err - } - return nil - }); err != nil { - return nil, err - } - - return u, nil -} - -// Delete a user from the UsersStore -func (s *UsersStore) Delete(ctx context.Context, u *chronograf.User) error { - _, err := s.get(ctx, u.ID) - if err != nil { - return err - } - return s.client.db.Update(func(tx *bolt.Tx) error { - return tx.Bucket(UsersBucket).Delete(u64tob(u.ID)) - }) -} - -// Update a user -func (s *UsersStore) Update(ctx context.Context, u *chronograf.User) error { - _, err := s.get(ctx, u.ID) - if err != nil { - return err - } - return s.client.db.Update(func(tx *bolt.Tx) error { - if v, err := internal.MarshalUser(u); err != nil { - return err - } else if err := tx.Bucket(UsersBucket).Put(u64tob(u.ID), v); err != nil { - return err - } - return nil - }) -} - -// All returns all users -func (s *UsersStore) All(ctx context.Context) ([]chronograf.User, error) { - var users []chronograf.User - if err := s.client.db.View(func(tx *bolt.Tx) error { - return tx.Bucket(UsersBucket).ForEach(func(k, v []byte) error { - var user chronograf.User - if err := internal.UnmarshalUser(v, &user); err != nil { - return err - } - users = append(users, user) - return nil - }) - }); err != nil { - return nil, err - } - - return users, nil -} diff --git a/chronograf/bolt/users_test.go b/chronograf/bolt/users_test.go deleted file mode 100644 index e2b38b47d72..00000000000 --- a/chronograf/bolt/users_test.go +++ /dev/null @@ -1,564 +0,0 @@ -package bolt_test - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/influxdata/influxdb/v2/chronograf" -) - -// IgnoreFields is used because ID is created by BoltDB and cannot be predicted reliably -// EquateEmpty is used because we want nil slices, arrays, and maps to be equal to the empty map -var cmpOptions = cmp.Options{ - cmpopts.IgnoreFields(chronograf.User{}, "ID"), - cmpopts.EquateEmpty(), -} - -func TestUsersStore_GetWithID(t *testing.T) { - type args struct { - ctx context.Context - usr *chronograf.User - } - tests := []struct { - name string - args args - want *chronograf.User - wantErr bool - addFirst bool - }{ - { - name: "User not found", - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - ID: 1337, - }, - }, - wantErr: true, - }, - { - name: "Get user", - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - }, - }, - want: &chronograf.User{ - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - }, - addFirst: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.UsersStore - if tt.addFirst { - tt.args.usr, err = s.Add(tt.args.ctx, tt.args.usr) - if err != nil { - t.Fatal(err) - } - } - got, err := s.Get(tt.args.ctx, chronograf.UserQuery{ID: &tt.args.usr.ID}) - if (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if diff := cmp.Diff(got, tt.want, cmpOptions...); diff != "" { - t.Errorf("%q. UsersStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestUsersStore_GetWithNameProviderScheme(t *testing.T) { - type args struct { - ctx context.Context - usr *chronograf.User - } - tests := []struct { - name string - args args - want *chronograf.User - wantErr bool - addFirst bool - }{ - { - name: "User not found", - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - }, - }, - wantErr: true, - }, - { - name: "Get user", - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - }, - }, - want: &chronograf.User{ - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - }, - addFirst: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.UsersStore - if tt.addFirst { - tt.args.usr, err = s.Add(tt.args.ctx, tt.args.usr) - if err != nil { - t.Fatal(err) - } - } - - got, err := s.Get(tt.args.ctx, chronograf.UserQuery{ - Name: &tt.args.usr.Name, - Provider: &tt.args.usr.Provider, - Scheme: &tt.args.usr.Scheme, - }) - if (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if diff := cmp.Diff(got, tt.want, cmpOptions...); diff != "" { - t.Errorf("%q. UsersStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestUsersStore_GetInvalid(t *testing.T) { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.UsersStore - - _, err = s.Get(context.Background(), chronograf.UserQuery{}) - if err == nil { - t.Errorf("Invalid Get. UsersStore.Get() error = %v", err) - } -} - -func TestUsersStore_Add(t *testing.T) { - type args struct { - ctx context.Context - u *chronograf.User - addFirst bool - } - tests := []struct { - name string - args args - want *chronograf.User - wantErr bool - }{ - { - name: "Add new user", - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "editor", - }, - }, - }, - }, - want: &chronograf.User{ - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "editor", - }, - }, - }, - }, - { - name: "User already exists", - args: args{ - ctx: context.Background(), - addFirst: true, - u: &chronograf.User{ - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "editor", - }, - }, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.UsersStore - if tt.args.addFirst { - _, _ = s.Add(tt.args.ctx, tt.args.u) - } - got, err := s.Add(tt.args.ctx, tt.args.u) - if (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Add() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - - if tt.wantErr { - continue - } - - got, err = s.Get(tt.args.ctx, chronograf.UserQuery{ID: &got.ID}) - if err != nil { - t.Fatalf("failed to get user: %v", err) - } - if diff := cmp.Diff(got, tt.want, cmpOptions...); diff != "" { - t.Errorf("%q. UsersStore.Add():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestUsersStore_Delete(t *testing.T) { - type args struct { - ctx context.Context - user *chronograf.User - } - tests := []struct { - name string - args args - addFirst bool - wantErr bool - }{ - { - name: "No such user", - args: args{ - ctx: context.Background(), - user: &chronograf.User{ - ID: 10, - }, - }, - wantErr: true, - }, - { - name: "Delete new user", - args: args{ - ctx: context.Background(), - user: &chronograf.User{ - Name: "noone", - }, - }, - addFirst: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.UsersStore - - if tt.addFirst { - tt.args.user, _ = s.Add(tt.args.ctx, tt.args.user) - } - if err := s.Delete(tt.args.ctx, tt.args.user); (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Delete() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - } -} - -func TestUsersStore_Update(t *testing.T) { - type args struct { - ctx context.Context - usr *chronograf.User - roles []chronograf.Role - provider string - scheme string - name string - } - tests := []struct { - name string - args args - addFirst bool - want *chronograf.User - wantErr bool - }{ - { - name: "No such user", - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - ID: 10, - }, - }, - wantErr: true, - }, - { - name: "Update user role", - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "viewer", - }, - }, - }, - roles: []chronograf.Role{ - { - Name: "editor", - }, - }, - }, - want: &chronograf.User{ - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "editor", - }, - }, - }, - addFirst: true, - }, - { - name: "Update user provider and scheme", - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - }, - provider: "google", - scheme: "oauth2", - name: "billietta", - }, - want: &chronograf.User{ - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - }, - addFirst: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.UsersStore - - if tt.addFirst { - tt.args.usr, err = s.Add(tt.args.ctx, tt.args.usr) - if err != nil { - t.Fatal(err) - } - } - - if tt.args.roles != nil { - tt.args.usr.Roles = tt.args.roles - } - - if tt.args.provider != "" { - tt.args.usr.Provider = tt.args.provider - } - - if tt.args.scheme != "" { - tt.args.usr.Scheme = tt.args.scheme - } - - if tt.args.name != "" { - tt.args.usr.Name = tt.args.name - } - - if err := s.Update(tt.args.ctx, tt.args.usr); (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - - // for the empty test - if tt.want == nil { - continue - } - - got, err := s.Get(tt.args.ctx, chronograf.UserQuery{ID: &tt.args.usr.ID}) - if err != nil { - t.Fatalf("failed to get user: %v", err) - } - if diff := cmp.Diff(got, tt.want, cmpOptions...); diff != "" { - t.Errorf("%q. UsersStore.Update():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestUsersStore_All(t *testing.T) { - tests := []struct { - name string - ctx context.Context - want []chronograf.User - addFirst bool - wantErr bool - }{ - { - name: "No users", - }, - { - name: "Update new user", - want: []chronograf.User{ - { - Name: "howdy", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "viewer", - }, - }, - }, - { - Name: "doody", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "editor", - }, - }, - }, - }, - addFirst: true, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.UsersStore - - if tt.addFirst { - for _, u := range tt.want { - s.Add(tt.ctx, &u) - } - } - gots, err := s.All(tt.ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - for i, got := range gots { - if diff := cmp.Diff(got, tt.want[i], cmpOptions...); diff != "" { - t.Errorf("%q. UsersStore.All():\n-got/+want\ndiff %s", tt.name, diff) - } - } - } -} - -func TestUsersStore_Num(t *testing.T) { - tests := []struct { - name string - ctx context.Context - users []chronograf.User - want int - wantErr bool - }{ - { - name: "No users", - want: 0, - }, - { - name: "Update new user", - want: 2, - users: []chronograf.User{ - { - Name: "howdy", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "viewer", - }, - }, - }, - { - Name: "doody", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "editor", - }, - }, - }, - }, - }, - } - for _, tt := range tests { - client, err := NewTestClient() - if err != nil { - t.Fatal(err) - } - defer client.Close() - - s := client.UsersStore - - for _, u := range tt.users { - s.Add(tt.ctx, &u) - } - got, err := s.Num(tt.ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Num() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - t.Errorf("%q. UsersStore.Num() = %d. want %d", tt.name, got, tt.want) - } - } -} diff --git a/chronograf/bolt/util.go b/chronograf/bolt/util.go deleted file mode 100644 index 660aad01aa4..00000000000 --- a/chronograf/bolt/util.go +++ /dev/null @@ -1,19 +0,0 @@ -package bolt - -import ( - "encoding/binary" -) - -// itob returns an 8-byte big endian representation of v. -func itob(v int) []byte { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, uint64(v)) - return b -} - -// u64tob returns an 8-byte big endian representation of v. -func u64tob(v uint64) []byte { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, v) - return b -} diff --git a/chronograf/canned/Makefile b/chronograf/canned/Makefile deleted file mode 100644 index 5d58079d759..00000000000 --- a/chronograf/canned/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# List any generated files here -TARGETS = bin_gen.go -# List any source files used to generate the targets here -SOURCES = bin.go $(shell find . -name '*.json') -# List any directories that have their own Makefile here -SUBDIRS = - -# Default target -all: $(SUBDIRS) $(TARGETS) - -# Recurse into subdirs for same make goal -$(SUBDIRS): - $(MAKE) -C $@ $(MAKECMDGOALS) - -# Clean all targets recursively -clean: $(SUBDIRS) - rm -f $(TARGETS) - -# Define go generate if not already defined -GO_GENERATE := go generate - -# Run go generate for the targets -$(TARGETS): $(SOURCES) - $(GO_GENERATE) -x - -.PHONY: all clean $(SUBDIRS) diff --git a/chronograf/canned/README.md b/chronograf/canned/README.md deleted file mode 100644 index 6a80244c479..00000000000 --- a/chronograf/canned/README.md +++ /dev/null @@ -1,7 +0,0 @@ -## Canned Applications -The JSON application layouts in this directory ship with the application as nice, default layouts and queries for telegraf data. - -### Create new Application - -To create a new application in this directory run `./new_apps.sh`. This shell script will create a new application template with a generated UUID. -Update this layout application file's queries, measurements, and application name. diff --git a/chronograf/canned/TODO.go b/chronograf/canned/TODO.go deleted file mode 100644 index be35a0332b8..00000000000 --- a/chronograf/canned/TODO.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build !assets - -package canned - -import "errors" - -// The functions defined in this file are placeholders when the binary is compiled -// without assets. - -// Asset returns an error stating no assets were included in the binary. -func Asset(string) ([]byte, error) { - return nil, errors.New("no assets included in binary") -} - -// AssetNames returns nil because there are no assets included in the binary. -func AssetNames() []string { - return nil -} diff --git a/chronograf/canned/apache.json b/chronograf/canned/apache.json deleted file mode 100644 index 971460b189a..00000000000 --- a/chronograf/canned/apache.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "id": "6dfb4d49-20dc-4157-9018-2b1b1cb75c2d", - "measurement": "apache", - "app": "apache", - "autoflow": false, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0246e457-916b-43e3-be99-211c4cbc03e8", - "name": "Apache Bytes/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"BytesPerSec\")) AS \"bytes_per_sec\" FROM \":db:\".\":rp:\".\"apache\"", - "label": "bytes/s", - "groupbys": [ - "\"server\"" - ], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "37f2e4bb-9fa5-4891-a424-9df5ce7458bb", - "name": "Apache - Requests/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"ReqPerSec\")) AS \"req_per_sec\" FROM \":db:\".\":rp:\".\"apache\"", - "label": "requests/s", - "groupbys": [ - "\"server\"" - ], - "wheres": [] - } - ] - }, - { - "x": 8, - "y": 0, - "w": 4, - "h": 4, - "i": "ea9174b3-2b56-4e80-a37d-064507c6775a", - "name": "Apache - Total Accesses", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"TotalAccesses\")) AS \"tot_access\" FROM \":db:\".\":rp:\".\"apache\"", - "label": "accesses/s", - "groupbys": [ - "\"server\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/bin.go b/chronograf/canned/bin.go deleted file mode 100644 index 8ba3db45f48..00000000000 --- a/chronograf/canned/bin.go +++ /dev/null @@ -1,83 +0,0 @@ -package canned - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -//go:generate env GO111MODULE=on go run github.com/kevinburke/go-bindata/go-bindata -o bin_gen.go -tags assets -ignore README|apps|.sh|go -pkg canned . - -// BinLayoutsStore represents a layout store using data generated by go-bindata -type BinLayoutsStore struct { - Logger chronograf.Logger -} - -// All returns the set of all layouts -func (s *BinLayoutsStore) All(ctx context.Context) ([]chronograf.Layout, error) { - names := AssetNames() - layouts := make([]chronograf.Layout, len(names)) - for i, name := range names { - octets, err := Asset(name) - if err != nil { - s.Logger. - WithField("component", "apps"). - WithField("name", name). - Error("Invalid Layout: ", err) - return nil, chronograf.ErrLayoutInvalid - } - - var layout chronograf.Layout - if err = json.Unmarshal(octets, &layout); err != nil { - s.Logger. - WithField("component", "apps"). - WithField("name", name). - Error("Unable to read layout:", err) - return nil, chronograf.ErrLayoutInvalid - } - layouts[i] = layout - } - - return layouts, nil -} - -// Add is not support by BinLayoutsStore -func (s *BinLayoutsStore) Add(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error) { - return chronograf.Layout{}, fmt.Errorf("add to BinLayoutsStore not supported") -} - -// Delete is not support by BinLayoutsStore -func (s *BinLayoutsStore) Delete(ctx context.Context, layout chronograf.Layout) error { - return fmt.Errorf("delete to BinLayoutsStore not supported") -} - -// Get retrieves Layout if `ID` exists. -func (s *BinLayoutsStore) Get(ctx context.Context, ID string) (chronograf.Layout, error) { - layouts, err := s.All(ctx) - if err != nil { - s.Logger. - WithField("component", "apps"). - WithField("name", ID). - Error("Invalid Layout: ", err) - return chronograf.Layout{}, chronograf.ErrLayoutInvalid - } - - for _, layout := range layouts { - if layout.ID == ID { - return layout, nil - } - } - - s.Logger. - WithField("component", "apps"). - WithField("name", ID). - Error("Layout not found") - return chronograf.Layout{}, chronograf.ErrLayoutNotFound -} - -// Update not supported -func (s *BinLayoutsStore) Update(ctx context.Context, layout chronograf.Layout) error { - return fmt.Errorf("update to BinLayoutsStore not supported") -} diff --git a/chronograf/canned/consul.json b/chronograf/canned/consul.json deleted file mode 100644 index a23d44700b6..00000000000 --- a/chronograf/canned/consul.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "id": "f3bec493-0bc1-49d5-a40a-a09bd5cfb60c", - "measurement": "consul_health_checks", - "app": "consul", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9e14639d-b8d9-4245-8c45-862ed4383d31", - "name": "Consul – Number of Critical Health Checks", - "queries": [ - { - "query": "SELECT count(\"check_id\") as \"Number Critical\" FROM \":db:\".\":rp:\".\"consul_health_checks\"", - "label": "count", - "groupbys": [ - "\"service_name\"" - ], - "wheres": [ - "\"status\" = 'critical'" - ] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "595be39d-85db-4410-b349-35c25465a4b8", - "name": "Consul – Number of Warning Health Checks", - "queries": [ - { - "query": "SELECT count(\"check_id\") as \"Number Warning\" FROM \":db:\".\":rp:\".\"consul_health_checks\"", - "label": "count", - "groupbys": [ - "\"service_name\"" - ], - "wheres": [ - "\"status\" = 'warning'" - ] - } - ] - } - ] -} diff --git a/chronograf/canned/consul_agent.json b/chronograf/canned/consul_agent.json deleted file mode 100644 index ade0ff36059..00000000000 --- a/chronograf/canned/consul_agent.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "id": "f3bec493-0bc1-49d5-a40a-a09bd5cfb700", - "measurement": "consul_consul_fsm_register", - "app": "consul_telemetry", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9e14639d-b8d9-4245-8c45-862ed4383701", - "name": "Consul Agent – Number of Go Routines", - "queries": [ - { - "query": "SELECT max(\"value\") AS \"Go Routines\" FROM \":db:\".\":rp:\".\"consul_ip-172-31-6-247_runtime_num_goroutines\"", - "groupbys": [ - ], - "wheres": [ - ] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9e14639d-b8d9-4245-8c45-862ed4383702", - "name": "Consul Agent – Runtime Alloc Bytes", - "queries": [ - { - "query": "SELECT max(\"value\") AS \"Runtime Alloc Bytes\" FROM \":db:\".\":rp:\".\"consul_ip-172-31-6-247_runtime_alloc_bytes\"", - "groupbys": [ - ], - "wheres": [ - ] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9e14639d-b8d9-4245-8c45-862ed4383703", - "name": "Consul Agent – Heap Objects", - "queries": [ - { - "query": "SELECT max(\"value\") AS \"Heap Objects\" FROM \":db:\".\":rp:\".\"consul_ip-172-31-6-247_runtime_heap_objects\"", - "groupbys": [ - ], - "wheres": [ - ] - } - ] - } - ] -} diff --git a/chronograf/canned/consul_cluster.json b/chronograf/canned/consul_cluster.json deleted file mode 100644 index 1fce31b7f60..00000000000 --- a/chronograf/canned/consul_cluster.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "350b780c-7d32-4b29-ac49-0d4e2c092943", - "measurement": "consul_memberlist_msg_alive", - "app": "consul_telemetry", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "bd62186a-f475-478b-bf02-8c4ab07eccd1", - "name": "Consul – Number of Agents", - "queries": [ - { - "query": "SELECT min(\"value\") AS \"num_agents\" FROM \":db:\".\":rp:\".\"consul_memberlist_msg_alive\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/chronograf/canned/consul_election.json b/chronograf/canned/consul_election.json deleted file mode 100644 index 02ae36fba2a..00000000000 --- a/chronograf/canned/consul_election.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "b15aaf24-701a-4d9b-920c-9a407e91da71", - "measurement": "consul_raft_state_candidate", - "app": "consul_telemetry", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "5b2bddce-badb-4594-91fb-0486f62266e5", - "name": "Consul – Leadership Election", - "queries": [ - { - "query": "SELECT max(\"value\") AS \"max_value\" FROM \":db:\".\":rp:\".\"consul_raft_state_candidate\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/chronograf/canned/consul_http.json b/chronograf/canned/consul_http.json deleted file mode 100644 index 624e175131b..00000000000 --- a/chronograf/canned/consul_http.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "26809869-8df3-49ad-b2f0-b1e1c72f67b0", - "measurement": "consul_consul_http_GET_v1_health_state__", - "app": "consul_telemetry", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "dfb4c50f-547e-484a-944b-d6374ba2b4c0", - "name": "Consul – HTTP Request Time (ms)", - "queries": [ - { - "query": "SELECT max(\"upper\") AS \"GET_health_state\" FROM \":db:\".\":rp:\".\"consul_consul_http_GET_v1_health_state__\"", - "label": "ms", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/chronograf/canned/consul_leadership.json b/chronograf/canned/consul_leadership.json deleted file mode 100644 index cdd6f9adfad..00000000000 --- a/chronograf/canned/consul_leadership.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "34611ae0-7c3e-4697-8db0-371b16bef345", - "measurement": "consul_raft_state_leader", - "app": "consul_telemetry", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "ef8eeeb5-b408-46d6-8cfc-20c00c9d7239", - "name": "Consul – Leadership Change", - "queries": [ - { - "query": "SELECT max(\"value\") as \"change\" FROM \":db:\".\":rp:\".\"consul_raft_state_leader\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/chronograf/canned/consul_serf_events.json b/chronograf/canned/consul_serf_events.json deleted file mode 100644 index 87853b961e5..00000000000 --- a/chronograf/canned/consul_serf_events.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "ef4b596c-77de-41c5-bb5b-d5c9a69fa633", - "measurement": "consul_serf_events", - "app": "consul_telemetry", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "59df3d73-5fac-48cb-84f1-dbe9a1bb886c", - "name": "Consul – Number of serf events", - "queries": [ - { - "query": "SELECT max(\"value\") AS \"serf_events\" FROM \":db:\".\":rp:\".\"consul_serf_events\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/chronograf/canned/cpu.json b/chronograf/canned/cpu.json deleted file mode 100644 index 46b1d28a28a..00000000000 --- a/chronograf/canned/cpu.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "id": "0fa47984-825b-46f1-9ca5-0366e3281cc5", - "measurement": "cpu", - "app": "system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "cc9ba2b6-e398-4396-80dc-819bb7ac7ce1", - "name": "CPU Usage", - "queries": [ - { - "query": "SELECT 100 - mean(\"usage_idle\") AS \"usage\" FROM \":db:\".\":rp:\".\"cpu\"", - "label": "% CPU time", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/disk.json b/chronograf/canned/disk.json deleted file mode 100644 index 1c2fa1c4c96..00000000000 --- a/chronograf/canned/disk.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "id": "0b75be4e-3454-4d5d-9a98-ca77c81397f6", - "measurement": "disk", - "app": "system", - "autoflow": true, - "cells": [ - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "5825a4dd-df97-4e99-a99d-67b68833c183", - "name": "System - Disk used %", - "queries": [ - { - "query": "SELECT mean(\"used_percent\") AS \"used_percent\" FROM \":db:\".\":rp:\".\"disk\"", - "label": "% used", - "groupbys": [ - "\"path\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/diskio.json b/chronograf/canned/diskio.json deleted file mode 100644 index 9ad163ba86b..00000000000 --- a/chronograf/canned/diskio.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "id": "9e3a9fcd-a363-4470-991e-a4d6987a94c8", - "measurement": "diskio", - "app": "system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "7f647740-d9f0-4012-8e7a-5d898c8f271e", - "name": "System – Disk MB/s", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"read_bytes\"), 1s) / 1000000 AS \"read_megabytes_per_second\" FROM \":db:\".\":rp:\".\"diskio\"", - "groupbys": [ - "\"name\"" - ], - "wheres": [], - "label": "MB/s" - }, - { - "query": "SELECT non_negative_derivative(max(\"write_bytes\"), 1s) / 1000000 AS \"write_megabytes_per_second\" FROM \":db:\".\":rp:\".\"diskio\"", - "groupbys": [ - "\"name\"" - ], - "wheres": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/chronograf/canned/docker.json b/chronograf/canned/docker.json deleted file mode 100644 index f3bbc1b8963..00000000000 --- a/chronograf/canned/docker.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "id": "0e980b97-c162-487b-a815-3f955df6243f", - "app": "docker", - "measurement": "docker", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "4c79cefb-5152-410c-9b88-74f9bff7ef22", - "name": "Docker - Container CPU %", - "queries": [ - { - "query": "SELECT mean(\"usage_percent\") AS \"usage_percent\" FROM \":db:\".\":rp:\".\"docker_container_cpu\"", - "label": "% CPU time", - "groupbys": [ - "\"container_name\"" - ] - } - ], - "type": "line-stacked" - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "4c79cefb-5152-410c-9b88-74f9bff7ef00", - "name": "Docker - Container Memory (MB)", - "queries": [ - { - "query": "SELECT mean(\"usage\") / 1048576 AS \"usage\" FROM \":db:\".\":rp:\".\"docker_container_mem\"", - "label": "MB", - "groupbys": [ - "\"container_name\"" - ] - } - ], - "type": "line-stepplot" - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "4c79cefb-5152-410c-9b88-74f9bff7ef01", - "name": "Docker - Containers", - "queries": [ - { - "query": "SELECT max(\"n_containers\") AS \"max_n_containers\" FROM \":db:\".\":rp:\".\"docker\"", - "label": "count", - "groupbys": [ - "\"host\"" - ] - } - ], - "colors": [], - "type": "single-stat" - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "4c79cefb-5152-410c-9b88-74f9bff7ef02", - "name": "Docker - Images", - "queries": [ - { - "query": "SELECT max(\"n_images\") AS \"max_n_images\" FROM \":db:\".\":rp:\".\"docker\"", - "groupbys": [ - "\"host\"" - ] - } - ], - "colors": [], - "type": "single-stat" - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "4c79cefb-5152-410c-9b88-74f9bff7ef03", - "name": "Docker - Container State", - "queries": [ - { - "query": "SELECT max(\"n_containers_running\") AS \"max_n_containers_running\" FROM \":db:\".\":rp:\".\"docker\"", - "label": "count", - "groupbys": [ - "\"host\"" - ] - }, - { - "query": "SELECT max(\"n_containers_stopped\") AS \"max_n_containers_stopped\" FROM \":db:\".\":rp:\".\"docker\"", - "groupbys": [ - "\"host\"" - ] - }, - { - "query": "SELECT max(\"n_containers_paused\") AS \"max_n_containers_paused\" FROM \":db:\".\":rp:\".\"docker\"", - "groupbys": [ - "\"host\"" - ] - } - ], - "type": "" - } - ] -} diff --git a/chronograf/canned/docker_blkio.json b/chronograf/canned/docker_blkio.json deleted file mode 100644 index 71cd5890d34..00000000000 --- a/chronograf/canned/docker_blkio.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "id": "0e980b97-c162-487b-a815-3f955df62440", - "measurement": "docker_container_blkio", - "app": "docker", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "4c79cefb-5152-410c-9b88-74f9bff7ef50", - "name": "Docker - Container Block IO", - "queries": [ - { - "query": "SELECT max(\"io_serviced_recursive_read\") AS \"max_io_read\" FROM \":db:\".\":rp:\".\"docker_container_blkio\"", - "groupbys": [ - "\"container_name\"" - ], - "wheres": [] - }, - { - "query": "SELECT max(\"io_serviced_recursive_sync\") AS \"max_io_sync\" FROM \":db:\".\":rp:\".\"docker_container_blkio\"", - "groupbys": [ - "\"container_name\"" - ], - "wheres": [] - }, - { - "query": "SELECT max(\"io_serviced_recursive_write\") AS \"max_io_write\" FROM \":db:\".\":rp:\".\"docker_container_blkio\"", - "groupbys": [ - "\"container_name\"" - ], - "wheres": [] - }, - { - "query": "SELECT max(\"io_serviced_recursive_total\") AS \"max_io_total\" FROM \":db:\".\":rp:\".\"docker_container_blkio\"", - "groupbys": [ - "\"container_name\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/docker_net.json b/chronograf/canned/docker_net.json deleted file mode 100644 index aa2b2f774d1..00000000000 --- a/chronograf/canned/docker_net.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "id": "0e980b97-c162-487b-a815-3f955df62430", - "measurement": "docker_container_net", - "app": "docker", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "4c79cefb-5152-410c-9b88-74f9bff7ef23", - "name": "Docker - Container Network", - "queries": [ - { - "query": "SELECT derivative(mean(\"tx_bytes\"), 10s) AS \"net_tx_bytes\" FROM \":db:\".\":rp:\".\"docker_container_net\"", - "groupbys": [ - "\"container_name\"" - ], - "wheres": [] - }, - { - "query": "SELECT derivative(mean(\"rx_bytes\"), 10s) AS \"net_rx_bytes\" FROM \":db:\".\":rp:\".\"docker_container_net\"", - "groupbys": [ - "\"container_name\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/elasticsearch.json b/chronograf/canned/elasticsearch.json deleted file mode 100644 index 12385d7c23b..00000000000 --- a/chronograf/canned/elasticsearch.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "id": "1f3ac9d0-bfb3-4e13-91a6-8949f7643ee9", - "measurement": "elasticsearch_indices", - "app": "elasticsearch", - "autoflow": false, - "cells": [ - { - "x": 0, - "y": 0, - "w": 12, - "h": 4, - "i": "3254c2ee-4b0f-440e-9cba-b996b96bf12a", - "name": "ElasticSearch - Query Throughput", - "queries": [ - { - "query": "select non_negative_derivative(mean(search_query_total)) as searches_per_min, non_negative_derivative(mean(search_scroll_total)) as scrolls_per_min, non_negative_derivative(mean(search_fetch_total)) as fetches_per_min, non_negative_derivative(mean(search_suggest_total)) as suggests_per_min from elasticsearch_indices", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 4, - "w": 12, - "h": 4, - "i": "7db341c0-455b-4595-8d34-61dfbdaf6cc6", - "name": "ElasticSearch - Open Connections", - "queries": [ - { - "query": "select mean(current_open) from elasticsearch_http", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 8, - "w": 6, - "h": 4, - "i": "ca304109-35db-4066-91e4-00875a618abb", - "name": "ElasticSearch - Query Latency", - "queries": [ - { - "query": "select non_negative_derivative(mean(search_query_time_in_millis)) as mean, non_negative_derivative(median(search_query_time_in_millis)) as median, non_negative_derivative(percentile(search_query_time_in_millis, 95)) as ninety_fifth from elasticsearch_indices", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 6, - "y": 8, - "w": 6, - "h": 4, - "i": "e0418118-a562-49d1-bf50-83943f72b245", - "name": "ElasticSearch - Fetch Latency", - "queries": [ - { - "query": "select non_negative_derivative(mean(search_fetch_time_in_millis)) as mean, non_negative_derivative(median(search_fetch_time_in_millis)) as median, non_negative_derivative(percentile(search_fetch_time_in_millis, 95)) as ninety_fifth from elasticsearch_indices", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 12, - "w": 6, - "h": 4, - "i": "3912091e-2ee5-4f47-bc74-40520239372d", - "name": "ElasticSearch - Suggest Latency", - "queries": [ - { - "query": "select non_negative_derivative(mean(search_suggest_time_in_millis)) as mean, non_negative_derivative(median(search_suggest_time_in_millis)) as median, non_negative_derivative(percentile(search_suggest_time_in_millis, 95)) as ninety_fifth from elasticsearch_indices", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 6, - "y": 12, - "w": 6, - "h": 4, - "i": "01e536cd-baf8-4bf3-9cee-9c1d149b58ef", - "name": "ElasticSearch - Scroll Latency", - "queries": [ - { - "query": "select non_negative_derivative(mean(search_scroll_time_in_millis)) as mean, non_negative_derivative(median(search_scroll_time_in_millis)) as median, non_negative_derivative(percentile(search_scroll_time_in_millis, 95)) as ninety_fifth from elasticsearch_indices", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 16, - "w": 12, - "h": 4, - "i": "306d6cdc-93ef-49d9-8151-a1bae355dfc6", - "name": "ElasticSearch - Indexing Latency", - "queries": [ - { - "query": "select non_negative_derivative(mean(indexing_index_time_in_millis)) as mean from elasticsearch_indices", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 20, - "w": 4, - "h": 4, - "i": "5ef57f9f-4cba-4f9e-9264-15aa2954c724", - "name": "ElasticSearch - JVM GC Collection Counts", - "queries": [ - { - "query": "select mean(gc_collectors_old_collection_count) as old_count, mean(gc_collectors_young_collection_count) as young_count from elasticsearch_jvm", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 20, - "w": 4, - "h": 4, - "i": "fa7c807e-3e87-4d26-869b-e0ffd3ef344a", - "name": "ElasticSearch - JVM GC Latency", - "queries": [ - { - "query": "select non_negative_derivative(mean(gc_collectors_old_collection_time_in_millis)) as mean_old_time, non_negative_derivative(mean(gc_collectors_young_collection_time_in_millis)) as mean_young_time from elasticsearch_jvm", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 8, - "y": 20, - "w": 4, - "h": 4, - "i": "6f4e01c4-31d6-4302-8e62-9f31f6c3f46f", - "name": "ElasticSearch - JVM Heap Usage", - "queries": [ - { - "query": "select mean(mem_heap_used_percent) from elasticsearch_jvm", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/haproxy.json b/chronograf/canned/haproxy.json deleted file mode 100644 index 7dd4391d148..00000000000 --- a/chronograf/canned/haproxy.json +++ /dev/null @@ -1,238 +0,0 @@ -{ - "id": "45c064fd-ebf7-45a1-bf8d-f53746d38a03", - "measurement": "haproxy", - "app": "haproxy", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "b846eda3-e068-4a34-91e9-c108c962a572", - "name": "HAProxy – Number of Servers", - "queries": [ - { - "query": "select mean(\"active_servers\") AS active_servers, mean(\"backup_servers\") AS backup_servers FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "a5070a99-c65a-4dfd-b486-2d3a2582d9eb", - "name": "HAProxy – Sum HTTP 2xx", - "queries": [ - { - "query": "SELECT non_negative_derivative(last(\"http_response.2xx\"), 1s) AS \"2xx\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "ab80deab-f9be-4506-b547-6f8286cb7660", - "name": "HAProxy – Sum HTTP 4xx", - "queries": [ - { - "query": "SELECT non_negative_derivative(last(\"http_response.4xx\"), 1s) AS \"4xx\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9754391d-3464-49cc-b3ef-de9332d3bc20", - "name": "HAProxy – Sum HTTP 5xx", - "queries": [ - { - "query": "SELECT non_negative_derivative(last(\"http_response.5xx\"), 1s) AS \"5xx\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "c836d118-6b03-436c-af60-0f95a5df0c89", - "name": "HAProxy – Frontend HTTP Requests/Second ", - "queries": [ - { - "query": "SELECT mean(\"req_rate\") AS \"requests_per_second\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "cc411bc8-8f14-43bb-865b-4b921310aef3", - "name": "HAProxy – Frontend Sessions/Second ", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"rate\")) AS \"sessions_per_second\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "3cc170b6-cd89-4142-b6a7-ea61b78bbdff", - "name": "HAProxy – Frontend Session Usage %", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"scur\")) / non_negative_derivative(max(\"slim\")) * 100 AS \"session_usage_percent\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "724db2a3-f23d-46d6-aa5b-f9e44cac1ee2", - "name": "HAProxy – Frontend Security Denials/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"dreq\")) AS \"denials_per_second\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "082a4e23-9256-441c-8414-db253a2c6d94", - "name": "HAProxy – Frontend Request Errors/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"ereq\")) AS \"errors_per_second\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "c7de430d-5684-494d-b735-0c87e7ea14e3", - "name": "HAProxy – Frontend Bytes/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"bin\")) AS \"bytes_in_per_second\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT non_negative_derivative(max(\"bout\")) AS \"bytes_out_per_second\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "cde02d86-4243-48d4-b812-46f8119b2ac5", - "name": "HAProxy – Backend Average Response Time (ms)", - "queries": [ - { - "query": "SELECT max(\"rtime\") AS \"response_time\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "2e8ef243-c993-4a53-b010-32de4beb1f81", - "name": "HAProxy – Backend Connection Errors/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"econ\")) AS \"errors_per_second\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "f4223249-d2fa-4778-bb27-449bf8863ea3", - "name": "HAProxy – Backend Queued Requests/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"qcur\")) AS \"queued_per_second\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "b3bcca49-7118-4f7e-921d-a8d47505795a", - "name": "HAProxy – Backend Average Request Queue Time (ms)", - "queries": [ - { - "query": "SELECT max(\"qtime\") AS \"queue_time\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "dd925132-3419-4677-9f21-a3d34cf25c99", - "name": "HAProxy – Backend Error Responses/Second", - "queries": [ - { - "query": "SELECT max(\"eresp\") AS \"error_response_rate\" FROM \":db:\".\":rp:\".\"haproxy\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/influxdb_database.json b/chronograf/canned/influxdb_database.json deleted file mode 100644 index 87e8bbc7c57..00000000000 --- a/chronograf/canned/influxdb_database.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "id": "543aa120-14ba-46a2-8ef9-6e6c7be3d600", - "measurement": "influxdb_database", - "app": "influxdb", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "ebc3e2df-640f-4344-b493-d5aae873b6d3", - "name": "InfluxDB - Cardinality", - "queries": [ - { - "query": "SELECT max(\"numMeasurements\") AS \"measurements\" FROM \":db:\".\":rp:\".\"influxdb_database\"", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT max(\"numSeries\") AS \"series\" FROM \":db:\".\":rp:\".\"influxdb_database\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/influxdb_httpd.json b/chronograf/canned/influxdb_httpd.json deleted file mode 100644 index 252c6936e02..00000000000 --- a/chronograf/canned/influxdb_httpd.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "id": "e0d70dc9-538a-4b29-8d27-4a76d5fc8a09", - "measurement": "influxdb_httpd", - "app": "influxdb", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "b4cbb2d6-a261-482a-942d-04e510f2b532", - "name": "InfluxDB - Write HTTP Requests", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"writeReq\")) AS \"http_requests\" FROM \":db:\".\":rp:\".\"influxdb_httpd\"", - "label": "count/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "cb473467-1854-4c7c-930e-769f24beb761", - "name": "InfluxDB - Query Requests", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"queryReq\")) AS \"query_requests\" FROM \":db:\".\":rp:\".\"influxdb_httpd\"", - "label": "count/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "type": "line-stepplot", - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "e0d70dc9-538a-4b29-8d27-4a76d5fc8a09", - "name": "InfluxDB - Client Failures", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"clientError\")) AS \"client_errors\" FROM \":db:\".\":rp:\".\"influxdb_httpd\"", - "label": "count/s", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT non_negative_derivative(max(\"authFail\"), 1s) AS \"auth_fail\" FROM \":db:\".\":rp:\".\"influxdb_httpd\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/influxdb_queryExecutor.json b/chronograf/canned/influxdb_queryExecutor.json deleted file mode 100644 index b280356a0bb..00000000000 --- a/chronograf/canned/influxdb_queryExecutor.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "id": "543aa120-14ba-46a2-8ef9-6e6c7be3d60e", - "measurement": "influxdb_queryExecutor", - "app": "influxdb", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "974f6948-d79a-4925-8162-193e6ddf1c7a", - "name": "InfluxDB - Query Performance", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"queryDurationNs\"), 1s) / 1000000 AS \"duration_ms\" FROM \":db:\".\":rp:\".\"influxdb_queryExecutor\"", - "label": "ms", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT non_negative_derivative(max(\"queriesExecuted\"), 1s) / 1000000 AS \"queries_executed_ms\" FROM \":db:\".\":rp:\".\"influxdb_queryExecutor\"", - "label": "ms", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/chronograf/canned/influxdb_write.json b/chronograf/canned/influxdb_write.json deleted file mode 100644 index f6dd6228b1a..00000000000 --- a/chronograf/canned/influxdb_write.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "id": "74fe93bf-14d6-40d4-af8f-335554f4acf3", - "measurement": "influxdb_write", - "app": "influxdb", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "12384232-7bc7-4129-8958-ef551a320524", - "name": "InfluxDB - Write Points", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"pointReq\")) AS \"points_written\" FROM \":db:\".\":rp:\".\"influxdb_write\"", - "label": "points/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "6281a48f-c29a-4941-bdd9-07f6d0fd98cf", - "name": "InfluxDB - Write Errors", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"writeError\")) AS \"shard_write_error\" FROM \":db:\".\":rp:\".\"influxdb_write\"", - "label": "errors/s", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT non_negative_derivative(max(\"serveError\")) AS \"http_error\" FROM \":db:\".\":rp:\".\"influxdb_httpd\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/kubernetes_node.json b/chronograf/canned/kubernetes_node.json deleted file mode 100644 index e427c7f3d0f..00000000000 --- a/chronograf/canned/kubernetes_node.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "id": "4a1efaec-57cf-4aeb-8dea-8a015f8ec3c5", - "measurement": "kubernetes_node", - "app": "kubernetes", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "5f406919-14b8-4c01-b0ce-f8ed75310805", - "name": "K8s - Node Millicores", - "queries": [ - { - "query": "SELECT mean(\"cpu_usage_nanocores\") / 1000000 AS \"cpu_usage_millicores\" FROM \":db:\".\":rp:\".\"kubernetes_node\"", - "groupbys": [ - "\"node_name\"" - ], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "042d47cc-fcfd-4b26-a690-d81c0321d408", - "name": "K8s - Node Memory Bytes", - "queries": [ - { - "query": "SELECT mean(\"memory_usage_bytes\") AS \"memory_usage_bytes\" FROM \":db:\".\":rp:\".\"kubernetes_node\"", - "groupbys": [ - "\"node_name\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/kubernetes_pod_container.json b/chronograf/canned/kubernetes_pod_container.json deleted file mode 100644 index 650f0119ac9..00000000000 --- a/chronograf/canned/kubernetes_pod_container.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "id": "50a14fed-6d0c-4c8a-a142-ad9276bee245", - "measurement": "kubernetes_pod_container", - "app": "kubernetes", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "e2427235-c81b-42a1-afdf-80d340fc01f8", - "name": "K8s - Pod Millicores", - "queries": [ - { - "query": "SELECT mean(\"cpu_usage_nanocores\") / 1000000 AS \"cpu_usage_millicores\" FROM \":db:\".\":rp:\".\"kubernetes_pod_container\"", - "groupbys": [ - "\"pod_name\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "6edb8c61-f723-47ce-a7eb-904fc6fe066e", - "name": "K8s - Pod Memory Bytes", - "queries": [ - { - "query": "SELECT mean(\"memory_usage_bytes\") AS \"memory_usage_bytes\" FROM \":db:\".\":rp:\".\"kubernetes_pod_container\"", - "groupbys": [ - "\"pod_name\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/kubernetes_pod_network.json b/chronograf/canned/kubernetes_pod_network.json deleted file mode 100644 index 2eb7c099f8f..00000000000 --- a/chronograf/canned/kubernetes_pod_network.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "45845136-bcb7-41ad-a02e-c63e9d3452de", - "measurement": "kubernetes_pod_network", - "app": "kubernetes", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0e06ddcd-05dd-493f-9dba-a382300a7190", - "name": "K8s - Pod TX Bytes/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"tx_bytes\")) AS \"tx_bytes_per_second\" FROM \":db:\".\":rp:\".\"kubernetes_pod_network\"", - "groupbys": [ - "\"pod_name\"", - "\"host\"" - ], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "cc062b4c-70ca-4bd7-b372-398e734feb49", - "name": "K8s - Pod RX Bytes/Second ", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"rx_bytes\")) AS \"rx_bytes_per_second\" FROM \":db:\".\":rp:\".\"kubernetes_pod_network\"", - "groupbys": [ - "\"pod_name\"", - "\"host\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/kubernetes_system_container.json b/chronograf/canned/kubernetes_system_container.json deleted file mode 100644 index 848935cec0d..00000000000 --- a/chronograf/canned/kubernetes_system_container.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "id": "05dde59a-a52f-4ede-81fa-0c6011f29287", - "measurement": "kubernetes_system_container", - "app": "kubernetes", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "64cf0f60-e157-4c03-9d7e-c280a7e2695f", - "name": "K8s - Kubelet Millicores", - "queries": [ - { - "query": "SELECT mean(\"cpu_usage_nanocores\") / 1000000 AS \"cpu_usage_millicores\" FROM \":db:\".\":rp:\".\"kubernetes_system_container\"", - "groupbys": [], - "wheres": [ - "\"container_name\" = 'kubelet'" - ] - } - ] - }, - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "4a7454d1-4d60-4077-9e7b-8c915a00fe66", - "name": "K8s - Kubelet Memory Bytes", - "queries": [ - { - "query": "SELECT mean(\"memory_usage_bytes\") AS \"memory_usage_bytes\" FROM \":db:\".\":rp:\".\"kubernetes_system_container\"", - "groupbys": [], - "wheres": [ - "\"container_name\" = 'kubelet'" - ] - } - ] - } - ] -} diff --git a/chronograf/canned/load.json b/chronograf/canned/load.json deleted file mode 100644 index 33e672a66f0..00000000000 --- a/chronograf/canned/load.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "ec6c48f4-48ca-4ba7-a842-5b700e19f274", - "measurement": "system", - "app": "system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "6ec7e632-2c19-475c-8747-56feaacf46ce", - "name": "System Load", - "queries": [ - { - "query": "SELECT mean(\"load1\") AS \"load\" FROM \":db:\".\":rp:\".\"system\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/mem.json b/chronograf/canned/mem.json deleted file mode 100644 index 045ce2f505d..00000000000 --- a/chronograf/canned/mem.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "4a805493-f7ef-4da0-8de8-e78afd899722", - "measurement": "mem", - "app": "system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "e6e5063c-43d5-409b-a0ab-68da51ed3f28", - "name": "System - Memory Gigabytes Used", - "queries": [ - { - "query": "SELECT mean(\"used\") / 1073741824 AS \"used\", mean(\"available\") / 1073741824 AS \"available\" FROM \":db:\".\":rp:\".\"mem\"", - "label": "GB", - "groupbys": [], - "wheres": [] - } - ], - "type": "line-stacked" - } - ] -} diff --git a/chronograf/canned/memcached.json b/chronograf/canned/memcached.json deleted file mode 100644 index e15c75791b9..00000000000 --- a/chronograf/canned/memcached.json +++ /dev/null @@ -1,216 +0,0 @@ -{ - "id": "f280c8c7-0530-425c-b281-788d8ded7676", - "measurement": "memcached", - "app": "memcached", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af490", - "name": "Memcached - Current Connections", - "queries": [ - { - "query": "SELECT max(\"curr_connections\") AS \"current_connections\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af400", - "name": "Memcached - Get Hits/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"get_hits\")) AS \"get_hits\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "hits/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af405", - "name": "Memcached - Get Misses/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"get_misses\")) AS \"get_misses\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "misses/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af413", - "name": "Memcached - Delete Hits/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"delete_hits\")) AS \"delete_hits\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "deletes/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af412", - "name": "Memcached - Delete Misses/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"delete_misses\")) AS \"delete_misses\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "delete misses/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af411", - "name": "Memcached - Incr Hits/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"incr_hits\")) AS \"incr_hits\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "incr hits/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af510", - "name": "Memcached - Incr Misses/Second", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"incr_misses\")) AS \"incr_misses\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "incr misses/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af402", - "name": "Memcached - Current Items", - "queries": [ - { - "query": "SELECT max(\"curr_items\") AS \"current_items\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af403", - "name": "Memcached - Total Items", - "queries": [ - { - "query": "SELECT max(\"total_items\") AS \"total_items\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af404", - "name": "Memcached - Bytes Stored", - "queries": [ - { - "query": "SELECT max(\"bytes\") AS \"bytes\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "bytes", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af406", - "name": "Memcached - Bytes Read/Sec", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"bytes_read\")) AS \"bytes_read\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "bytes/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af407", - "name": "Memcached - Bytes Written/Sec", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"bytes_written\")) AS \"bytes_written\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "bytes/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1b5716f1-b9d1-4a8b-b3bb-6e7d120af401", - "name": "Memcached - Evictions/10 Seconds", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"evictions\"), 10s) AS \"evictions\" FROM \":db:\".\":rp:\".\"memcached\"", - "label": "evictions / 10s", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/mesos.json b/chronograf/canned/mesos.json deleted file mode 100644 index 85c4683fcdb..00000000000 --- a/chronograf/canned/mesos.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "id": "0fa47984-825b-46f1-9ca5-0366e3220000", - "measurement": "mesos", - "app": "mesos", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0fa47984-825b-46f1-9ca5-0366e3220007", - "name": "Mesos Active Slaves", - "queries": [ - { - "query": "SELECT max(\"master/slaves_active\") AS \"Active Slaves\" FROM \":db:\".\":rp:\".\"mesos\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0fa47984-825b-46f1-9ca5-0366e3220001", - "name": "Mesos Tasks Active", - "queries": [ - { - "query": "SELECT max(\"master/tasks_running\") AS \"num tasks\" FROM \":db:\".\":rp:\".\"mesos\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0fa47984-825b-46f1-9ca5-0366e3220004", - "name": "Mesos Tasks", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"master/tasks_finished\"), 60s) AS \"tasks finished\" FROM \":db:\".\":rp:\".\"mesos\"", - "label": "count", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT non_negative_derivative(max(\"master/tasks_failed\"), 60s) AS \"tasks failed\" FROM \":db:\".\":rp:\".\"mesos\"", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT non_negative_derivative(max(\"master/tasks_killed\"), 60s) AS \"tasks killed\" FROM \":db:\".\":rp:\".\"mesos\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0fa47984-825b-46f1-9ca5-0366e3220005", - "name": "Mesos Outstanding offers", - "queries": [ - { - "query": "SELECT max(\"master/outstanding_offers\") AS \"Outstanding Offers\" FROM \":db:\".\":rp:\".\"mesos\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0fa47984-825b-46f1-9ca5-0366e3220002", - "name": "Mesos Available/Used CPUs", - "queries": [ - { - "query": "SELECT max(\"master/cpus_total\") AS \"cpu total\", max(\"master/cpus_used\") AS \"cpu used\" FROM \":db:\".\":rp:\".\"mesos\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0fa47984-825b-46f1-9ca5-0366e3220003", - "name": "Mesos Available/Used Memory", - "queries": [ - { - "query": "SELECT max(\"master/mem_total\") AS \"memory total\", max(\"master/mem_used\") AS \"memory used\" FROM \":db:\".\":rp:\".\"mesos\"", - "label": "MB", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0fa47984-825b-46f1-9ca5-0366e3220008", - "name": "Mesos Master Uptime", - "colors": [], - "type": "single-stat", - "queries": [ - { - "query": "SELECT max(\"master/uptime_secs\") AS \"uptime\" FROM \":db:\".\":rp:\".\"mesos\"", - "label": "Seconds", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/mongodb.json b/chronograf/canned/mongodb.json deleted file mode 100644 index 3d2e7858a95..00000000000 --- a/chronograf/canned/mongodb.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "id": "921298ad-0cdd-44f4-839b-10c319e7fcc7", - "measurement": "mongodb", - "app": "mongodb", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "b2631fd5-7d32-4a31-9edf-98362fd3626e", - "name": "MongoDB – Read/Second", - "queries": [ - { - "query": "SELECT mean(queries_per_sec) AS queries_per_second, mean(getmores_per_sec) AS getmores_per_second FROM \":db:\".\":rp:\".\"mongodb\"", - "label": "reads/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9362e390-951b-4dba-adec-40c261e37604", - "name": "MongoDB – Writes/Second", - "queries": [ - { - "query": "SELECT mean(inserts_per_sec) AS inserts_per_second, mean(updates_per_sec) AS updates_per_second, mean(deletes_per_sec) AS deletes_per_second FROM \":db:\".\":rp:\".\"mongodb\"", - "label": "writes/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "7ca54d4c-9f0d-47fd-a7fe-2d01e832bbf4", - "name": "MongoDB – Active Connections", - "queries": [ - { - "query": "SELECT mean(open_connections) AS open_connections FROM \":db:\".\":rp:\".\"mongodb\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "ea5ae388-9ca3-42f9-835f-cc9b265705be", - "name": "MongoDB – Reads/Writes Waiting in Queue", - "queries": [ - { - "query": "SELECT max(queued_reads) AS queued_reads, max(queued_writes) as queued_writes FROM \":db:\".\":rp:\".\"mongodb\"", - "label": "count", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "631dcbba-c997-4fd7-b640-754a1b36026c", - "name": "MongoDB – Network Bytes/Second", - "queries": [ - { - "query": "SELECT mean(net_in_bytes) AS net_in_bytes, mean(net_out_bytes) as net_out_bytes FROM \":db:\".\":rp:\".\"mongodb\"", - "label": "bytes/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "5b03bef0-e5e9-4b53-b5f8-1d1b740cf5a2", - "name": "MongoDB – Page Faults", - "queries": [ - { - "query": "SELECT mean(page_faults_per_sec) AS page_faults_per_second FROM \":db:\".\":rp:\".\"mongodb\"", - "label": "faults/s", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "4bc98883-2347-46bb-9459-1c6fe7fb47a8", - "name": "MongoDB – Memory Usage (MB)", - "queries": [ - { - "query": "SELECT mean(vsize_megabytes) AS virtual_memory_megabytes, mean(resident_megabytes) as resident_memory_megabytes FROM \":db:\".\":rp:\".\"mongodb\"", - "label": "MB", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/mysql.json b/chronograf/canned/mysql.json deleted file mode 100644 index 0bc92dd911f..00000000000 --- a/chronograf/canned/mysql.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "id": "c1aa88c7-a047-4b52-85c4-0eec21b357ef", - "measurement": "mysql", - "app": "mysql", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "531192d3-f183-4481-afea-79103d56875a", - "name": "MySQL – Reads/Second", - "queries": [ - { - "query": - "SELECT non_negative_derivative(last(\"commands_select\"), 1s) AS selects_per_second FROM \":db:\".\":rp:\".\"mysql\"", - "groupbys": ["\"server\""], - "wheres": [] - }, - { - "query": - "SELECT non_negative_derivative(last(\"com_select\"), 1s) AS selects_per_second FROM \":db:\".\":rp:\".\"mysql\"", - "groupbys": ["\"server\""], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "2dc5e60e-6ddb-43cb-80c5-dfc9294dad97", - "name": "MySQL – Writes/Second", - "queries": [ - { - "query": - "SELECT non_negative_derivative(last(\"commands_insert\"), 1s) AS inserts_per_second, non_negative_derivative(last(\"commands_update\"), 1s) AS updates_per_second, non_negative_derivative(last(\"commands_delete\"), 1s) AS deletes_per_second FROM \":db:\".\":rp:\".\"mysql\"", - "groupbys": ["\"server\""], - "wheres": [] - }, - { - "query": - "SELECT non_negative_derivative(last(\"com_insert\"), 1s) AS inserts_per_second, non_negative_derivative(last(\"com_update\"), 1s) AS updates_per_second, non_negative_derivative(last(\"com_delete\"), 1s) AS deletes_per_second FROM \":db:\".\":rp:\".\"mysql\"", - "groupbys": ["\"server\""], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "2179fd37-6380-47dc-a1f3-959b69d8f9ec", - "name": "MySQL – Connections/Second", - "queries": [ - { - "query": - "SELECT non_negative_derivative(last(\"threads_connected\"), 1s) AS cxn_per_second, non_negative_derivative(last(\"threads_running\"), 1s) AS threads_running_per_second FROM \":db:\".\":rp:\".\"mysql\"", - "groupbys": ["\"server\""], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "b13816b7-041d-4387-b593-86898aa379ab", - "name": "MySQL – Connections Errors/Second", - "queries": [ - { - "query": - "SELECT non_negative_derivative(last(\"connection_errors_max_connections\"), 1s) AS cxn_errors_per_second, non_negative_derivative(last(\"connection_errors_internal\"), 1s) AS internal_cxn_errors_per_second, non_negative_derivative(last(\"aborted_connects\"), 1s) AS cxn_aborted_per_second FROM \":db:\".\":rp:\".\"mysql\"", - "groupbys": ["\"server\""], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/net.json b/chronograf/canned/net.json deleted file mode 100644 index c108e86d706..00000000000 --- a/chronograf/canned/net.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "id": "4585a7db-73af-4ca1-9378-47ee67c71f99", - "measurement": "net", - "app": "system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "e2f65d45-1898-4a16-860c-14b655575925", - "name": "System – Network Mb/s", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"bytes_recv\"), 1s) / 125000 as \"rx_megabits_per_second\" FROM \":db:\".\":rp:\".\"net\"", - "groupbys": [], - "wheres": [], - "label": "Mb/s" - }, - { - "query": "SELECT non_negative_derivative(max(\"bytes_sent\"), 1s) / 125000 as \"tx_megabits_per_second\" FROM \":db:\".\":rp:\".\"net\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "5e957624-b28b-4904-8068-5e7a9a058609", - "name": "System – Network Error Rate", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"err_in\"), 1s) / 125000 as \"tx_errors_per_second\" FROM \":db:\".\":rp:\".\"net\"", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT non_negative_derivative(max(\"err_out\"), 1s) / 125000 as \"rx_errors_per_second\" FROM \":db:\".\":rp:\".\"net\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/chronograf/canned/netstat.json b/chronograf/canned/netstat.json deleted file mode 100644 index f85a69eabbe..00000000000 --- a/chronograf/canned/netstat.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "id": "ff41d044-f61a-4522-8de7-9e39e3a1b5de", - "measurement": "netstat", - "app": "system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "cf5d0608-b513-4244-a55f-accf520da3a1", - "name": "System - Open Sockets", - "queries": [ - { - "query": "SELECT mean(\"tcp_established\") AS \"tcp_established\" FROM \":db:\".\":rp:\".\"netstat\"", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT mean(\"udp_socket\") AS \"udp_socket\" FROM \":db:\".\":rp:\".\"netstat\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "63503235-a588-49a7-ae0a-fb015c888e5b", - "name": "System - Sockets Created/Second ", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"tcp_established\")) AS \"tcp_established\" FROM \":db:\".\":rp:\".\"netstat\"", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT non_negative_derivative(max(\"udp_socket\")) AS \"udp_socket\" FROM \":db:\".\":rp:\".\"netstat\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/new_apps.sh b/chronograf/canned/new_apps.sh deleted file mode 100755 index 7dec94db93d..00000000000 --- a/chronograf/canned/new_apps.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/sh - -measurement= - -# Usage info -show_help() { - -cat << EOF -Usage: ${0##*/} MEASUREMENT -Generate new layout for MEASUREMENT. File created will be named -MEASUREMENT.json with UUID being generated from the uuidgen command. - - -h display this help and exit -EOF -} - -while :; do - case $1 in - -h|-\?|--help) # Call a "show_help" function to display a synopsis, then exit. - show_help - exit - ;; - *) # Default case: If no more options then break out of the loop. - measurement=$1 - break - esac - shift -done - -if [ -z "$measurement" ]; then - show_help - exit -fi - -CELLID=$(uuidgen | tr A-Z a-z) -UUID=$(uuidgen | tr A-Z a-z) -APP_FILE="$measurement".json -echo Creating measurement file $APP_FILE -cat > $APP_FILE << EOF -{ - "id": "$UUID", - "measurement": "$measurement", - "app": "$measurement", - "cells": [{ - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "$CELLID", - "name": "User facing cell Name", - "queries": [{ - "query": "select mean(\"used_percent\") from disk", - "groupbys": [], - "wheres": [] - }] - }] -} -EOF diff --git a/chronograf/canned/nginx.json b/chronograf/canned/nginx.json deleted file mode 100644 index 723431dfe7b..00000000000 --- a/chronograf/canned/nginx.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "id": "b805d661-e5a3-45e4-af18-de0e9360e6e7", - "measurement": "nginx", - "app": "nginx", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "a209be7f-33c6-4612-88b2-848ae402c66a", - "name": "NGINX – Client Connections", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"accepts\"), 1s) AS \"accepts\", non_negative_derivative(max(\"handled\"), 1s) AS \"handled\", non_negative_derivative(max(\"active\"), 1s) AS \"active\" FROM \":db:\".\":rp:\".\"nginx\"", - "groupbys": [ - "\"server\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "0fc591ad-8541-4de3-a36e-4ae69ff954c4", - "name": "NGINX – Client Errors", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"accepts\")) - non_negative_derivative(max(\"handled\")) FROM \":db:\".\":rp:\".\"nginx\"", - "groupbys": [ - "\"server\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "a1f37574-b86e-4278-8acc-ba78d3ac2e4e", - "name": "NGINX – Client Requests", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"requests\"), 1s) AS \"requests\" FROM \":db:\".\":rp:\".\"nginx\"", - "groupbys": [ - "\"server\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "5b91c5b0-d270-4d03-aeae-007f2351c80c", - "name": "NGINX – Active Client State", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"waiting\"), 1s) AS \"waiting\", non_negative_derivative(max(\"reading\"), 1s) AS \"reading\", non_negative_derivative(max(\"writing\"), 1s) AS \"writing\" FROM \":db:\".\":rp:\".\"nginx\"", - "groupbys": [ - "\"server\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/nsq_channel.json b/chronograf/canned/nsq_channel.json deleted file mode 100644 index ad23930918d..00000000000 --- a/chronograf/canned/nsq_channel.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "7b035812-182a-4a94-ba2e-902dfb81e0a2", - "measurement": "nsq_channel", - "app": "nsq", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "89dad9c8-3391-400e-a44a-b5d4a2c53bf1", - "name": "NSQ - Channel Client Count", - "queries": [ - { - "query": "SELECT mean(\"client_count\") AS \"client_count\" FROM \":db:\".\":rp:\".\"nsq_channel\"", - "groupbys": [ - "\"topic\"", - "\"channel\"" - ], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "e3eb48c0-8283-4445-b174-f4f8e4182e45", - "name": "NSQ - Channel Messages Count", - "queries": [ - { - "query": "SELECT mean(\"message_count\") AS \"message_count\" FROM \":db:\".\":rp:\".\"nsq_channel\"", - "groupbys": [ - "\"topic\"", - "\"channel\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/nsq_server.json b/chronograf/canned/nsq_server.json deleted file mode 100644 index 7e8eab2e4c6..00000000000 --- a/chronograf/canned/nsq_server.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "id": "6c351881-05ec-48f1-b11b-9c36d2c7cc80", - "measurement": "nsq_server", - "app": "nsq", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "c376a3d8-cd2a-4212-bf1d-da776b75feeb", - "name": "NSQ - Topic Count", - "queries": [ - { - "query": "SELECT mean(\"topic_count\") AS \"topic_count\" FROM \":db:\".\":rp:\".\"nsq_server\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "aa1aa20a-48aa-4a42-aaa0-426aa6a58aa8", - "name": "NSQ - Server Count", - "queries": [ - { - "query": "SELECT mean(\"server_count\") AS \"server_count\" FROM \":db:\".\":rp:\".\"nsq_server\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/nsq_topic.json b/chronograf/canned/nsq_topic.json deleted file mode 100644 index b1ce7454fac..00000000000 --- a/chronograf/canned/nsq_topic.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "id": "f7be6717-61df-4e58-ac4a-e4f49f95d847", - "measurement": "nsq_topic", - "app": "nsq", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "f07967cb-2c2a-41cb-8420-f041f46b0635", - "name": "NSQ - Topic Messages", - "queries": [ - { - "query": "SELECT mean(\"depth\") AS \"depth\" FROM \":db:\".\":rp:\".\"nsq_topic\"", - "groupbys": [ - "\"topic\"" - ], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "912f337b-3af2-42af-9352-b31a5bc3b431", - "name": "NSQ - Topic Messages on Disk", - "queries": [ - { - "query": "SELECT mean(\"backend_depth\") AS \"backend_depth\" FROM \":db:\".\":rp:\".\"nsq_topic\"", - "groupbys": [ - "\"topic\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 4, - "w": 4, - "h": 4, - "i": "06909f21-f035-4668-8193-8e06a018accb", - "name": "NSQ - Topic Ingress", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"message_count\")) AS \"messages_per_second\" FROM \":db:\".\":rp:\".\"nsq_topic\"", - "groupbys": [ - "\"topic\"", - "\"host\"" - ], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 4, - "w": 4, - "h": 4, - "i": "a5aa73a5-42aa-464a-aaaa-0a7a50632a0a", - "name": "NSQ topic egress", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"message_count\")) - non_negative_derivative(max(\"depth\")) AS \"messages_per_second\" FROM \":db:\".\":rp:\".\"nsq_topic\"", - "groupbys": [ - "\"topic\"", - "\"host\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/phpfpm.json b/chronograf/canned/phpfpm.json deleted file mode 100644 index b2a105eebc5..00000000000 --- a/chronograf/canned/phpfpm.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "id": "e6b69c66-6183-4728-9f1d-1b0f1fc01b7d", - "measurement": "phpfpm", - "app": "phpfpm", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "da42044d-8d10-4e3c-a0a2-41512266fd00", - "name": "phpfpm – Accepted Connections", - "queries": [ - { - "query": "SELECT non_negative_derivative(mean(\"accepted_conn\"),1s) FROM \":db:\".\":rp:\".\"phpfpm\"", - "label": "count", - "groupbys": [ - "\"pool\"" - ] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "7aae5ec6-dbaf-4926-b922-d585e6a869be", - "name": "phpfpm – Processes", - "queries": [ - { - "query": "SELECT mean(\"active_processes\") as \"active\",mean(\"idle_processes\") as \"idle\" FROM \":db:\".\":rp:\".\"phpfpm\"", - "label": "count", - "groupbys": [ - "\"pool\"" - ] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "e4de9091-7250-4634-bf38-81a441ef0f27", - "name": "phpfpm – Slow Requests", - "queries": [ - { - "query": "SELECT non_negative_derivative(mean(\"slow_requests\"),1s) FROM \":db:\".\":rp:\".\"phpfpm\"", - "label": "count", - "groupbys": [ - "\"pool\"" - ] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "7ed72ef0-a429-4edd-9c8e-a11625a279c2", - "name": "phpfpm – Max Children Reached", - "queries": [ - { - "query": "SELECT mean(\"max_children_reached\") FROM \":db:\".\":rp:\".\"phpfpm\"", - "label": "count", - "groupbys": [ - "\"pool\"" - ] - } - ] - } - ] -} diff --git a/chronograf/canned/ping.json b/chronograf/canned/ping.json deleted file mode 100644 index e732a05d874..00000000000 --- a/chronograf/canned/ping.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "id": "6fba9b06-b9d3-4e67-a41e-177d585dfe28", - "measurement": "ping", - "app": "ping", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "f58a157b-9f2f-4175-94c7-c250d9491c11", - "name": "Ping – Packet Loss Percent", - "queries": [ - { - "query": "select max(\"percent_packet_loss\") as \"packet_loss\" from ping", - "groupbys": [ - "\"url\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "865f646f-6ed9-4878-81f6-2b9e0d40697d", - "name": "Ping – Response Times (ms)", - "queries": [ - { - "query": "select mean(\"average_response_ms\") as \"average\", mean(\"minimum_response_ms\") as \"min\", mean(\"maximum_response_ms\") as \"max\" from ping", - "groupbys": [ - "\"url\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/postgresql.json b/chronograf/canned/postgresql.json deleted file mode 100644 index 5bf1b3586d9..00000000000 --- a/chronograf/canned/postgresql.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "id": "0975a073-9eb8-471c-aaf2-692b65f9fe5c", - "measurement": "postgresql", - "app": "postgresql", - "autoflow": false, - "cells": [ - { - "x": 0, - "y": 0, - "w": 12, - "h": 4, - "i": "b417bc9f-b16d-4691-91a7-85adfdd3e8ec", - "name": "PostgreSQL - Rows", - "queries": [ - { - "query": "SELECT non_negative_derivative(mean(\"tup_fetched\")) AS \"fetched\", non_negative_derivative(mean(\"tup_returned\")) AS \"returned\", non_negative_derivative(mean(\"tup_inserted\")) AS \"inserted\", non_negative_derivative(mean(\"tup_updated\")) AS \"updated\" FROM \":db:\".\":rp:\".\"postgresql\"", - "groupbys": [ - "db" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 8, - "w": 12, - "h": 4, - "i": "230d5baa-9376-438c-9a55-6f97f8c68e69", - "name": "PostgreSQL - QPS", - "queries": [ - { - "query": "SELECT non_negative_derivative(mean(\"xact_commit\")) AS \"xact_commit\" FROM \":db:\".\":rp:\".\"postgresql\"", - "groupbys": [ - "db" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 4, - "w": 6, - "h": 4, - "i": "4762130d-7005-467f-80ad-8c7f6dfe822e", - "name": "PostgreSQL - Buffers", - "queries": [ - { - "query": "SELECT mean(\"buffers_alloc\") AS \"buffers_allocated\", mean(\"buffers_backend\") AS \"buffers_backend\", mean(\"buffers_backend_fsync\") AS \"buffers_backend_fsync\", mean(\"buffers_checkpoint\") AS \"buffers_checkpoint\", mean(\"buffers_clean\") AS \"buffers_clean\" FROM \":db:\".\":rp:\".\"postgresql\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 6, - "y": 4, - "w": 6, - "h": 4, - "i": "95e73bda-7527-4aca-89dd-109cb6bb4294", - "name": "PostgreSQL - Conflicts/Deadlocks", - "queries": [ - { - "query": "SELECT mean(\"conflicts\") AS \"conflicts\", mean(\"deadlocks\") AS \"deadlocks\" FROM \":db:\".\":rp:\".\"postgresql\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/processes.json b/chronograf/canned/processes.json deleted file mode 100644 index 2b8af87a574..00000000000 --- a/chronograf/canned/processes.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "ffad2dff-d263-412e-806a-1e836af87942", - "measurement": "processes", - "app": "system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "84048146-f93d-4d6c-b7dd-c8e2a68abb27", - "name": "System - Total Processes", - "queries": [ - { - "query": "SELECT mean(\"total\") AS \"total\" FROM \":db:\".\":rp:\".\"processes\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/procstat.json b/chronograf/canned/procstat.json deleted file mode 100644 index aa162a0befe..00000000000 --- a/chronograf/canned/procstat.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "44644fae-21e7-4897-81e6-b11d2643cd61", - "measurement": "procstat", - "app": "system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "e75a6baa-9938-4ade-b83f-55a239039964", - "name": "Processes – Resident Memory (MB)", - "queries": [ - { - "query": "SELECT max(\"memory_rss\") / 1000000 AS \"max_mb_memory_rss\" FROM \":db:\".\":rp:\".\"procstat\"", - "groupbys": ["\"exe\""], - "wheres": [], - "label": "MB" - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "2bfae447-47c6-4f85-9fec-494301d29a04", - "name": "Processes – CPU Usage %", - "queries": [ - { - "query": "SELECT max(\"cpu_usage\") AS \"cpu_usage\" FROM \":db:\".\":rp:\".\"procstat\"", - "groupbys": ["\"exe\""], - "wheres": [], - "label": "%" - } - ] - } - ] -} \ No newline at end of file diff --git a/chronograf/canned/rabbitmq.json b/chronograf/canned/rabbitmq.json deleted file mode 100644 index fac94df3b20..00000000000 --- a/chronograf/canned/rabbitmq.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "id": "0c57a644-aa74-4ec3-b099-b44499df1159", - "measurement": "rabbitmq_node", - "app": "rabbitmq", - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "c46351c6-b33a-4dc2-a053-3517e7c8098e", - "name": "RabbitMQ - Overview", - "queries": [ - { - "query": "select mean(\"consumers\") AS \"consumers\" from rabbitmq_overview", - "groupbys": [], - "wheres": [] - }, - { - "query": "select mean(\"exchanges\") AS \"exchanges\" from rabbitmq_overview", - "groupbys": [], - "wheres": [] - }, - { - "query": "select mean(\"queues\") AS \"queues\" from rabbitmq_overview", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "c46351c6-b33a-4dc2-a053-3517e6c8098e", - "name": "RabbitMQ - Published/Delivered per second", - "queries": [ - { - "query": "select derivative(mean(\"messages_published\"), 1s) AS \"published_per_sec\" from rabbitmq_overview", - "groupbys": [], - "wheres": [] - }, - { - "query": "select derivative(mean(\"messages_delivered\"), 1s) AS \"delivered_per_sec\" from rabbitmq_overview", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "c46351c6-b33a-4dc2-a053-3547e7c8098e", - "name": "RabbitMQ - Acked/Unacked per second", - "queries": [ - { - "query": "select derivative(mean(\"messages_acked\"), 1s) AS \"acked_per_sec\" from rabbitmq_overview", - "groupbys": [], - "wheres": [] - }, - { - "query": "select derivative(mean(\"messages_unacked\"), 1s) AS \"unacked_per_sec\" from rabbitmq_overview", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/redis.json b/chronograf/canned/redis.json deleted file mode 100644 index 1a80b426324..00000000000 --- a/chronograf/canned/redis.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "id": "793e6cca-7d7f-48e4-8db2-7b81761cc6ff", - "measurement": "redis", - "app": "redis", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9c168ac8-2985-4883-bdf2-938ea9f065b9", - "name": "Redis - Connected Clients", - "queries": [ - { - "query": "SELECT mean(\"clients\") AS \"clients\" FROM \":db:\".\":rp:\".\"redis\"", - "groupbys": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9c168ac8-2985-4883-bdf2-938ea9f065a0", - "name": "Redis - Blocked Clients", - "queries": [ - { - "query": "SELECT mean(\"blocked_clients\") AS \"blocked_clients\" FROM \":db:\".\":rp:\".\"redis\"", - "groupbys": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9c168ac8-2985-4883-bdf2-938ea9f065b1", - "name": "Redis - CPU", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"used_cpu_user\")) AS \"used_cpu_per_second\" FROM \":db:\".\":rp:\".\"redis\"", - "groupbys": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "9c168ac8-2985-4883-bdf2-938ea9f065b2", - "name": "Redis - Memory", - "queries": [ - { - "query": "SELECT non_negative_derivative(max(\"used_memory\")) AS \"used_memory_per_second\" FROM \":db:\".\":rp:\".\"redis\"", - "groupbys": [] - } - ] - } - ] -} diff --git a/chronograf/canned/riak.json b/chronograf/canned/riak.json deleted file mode 100644 index b52b4efdd2c..00000000000 --- a/chronograf/canned/riak.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "id": "f56fd522-3e9c-492d-88fe-34e05d6d2462", - "measurement": "riak", - "app": "riak", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "e12ebb94-2592-4b83-86fc-1f8a9aa84262", - "name": "Riak – Total Memory Bytes", - "queries": [ - { - "query": "SELECT max(\"memory_total\") as memory_total_bytes FROM \":db:\".\":rp:\".\"riak\"", - "groupbys": [ - "\"nodename\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "8355d65d-34a7-4b6e-ae54-eaf25cd14e4b", - "name": "Riak – Object Byte Size", - "queries": [ - { - "query": "SELECT max(\"node_get_fsm_objsize_median\") AS \"median\", max(\"node_get_fsm_objsize_100\") AS \"100th-percentile\", max(\"node_get_fsm_objsize_99\") AS \"99th-percentile\", max(\"node_get_fsm_objsize_mean\") AS \"mean\", max(\"node_get_fsm_objsize_95\") AS \"95th-percentile\" FROM \":db:\".\":rp:\".\"riak\"", - "groupbys": [ - "\"nodename\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "91e26cbe-1595-4d17-a54b-c26e08ecf572", - "name": "Riak – Number of Siblings/Minute", - "queries": [ - { - "query": "SELECT max(\"node_get_fsm_siblings_median\") AS \"median\", max(\"node_get_fsm_siblings_mean\") AS \"mean\", max(\"node_get_fsm_siblings_99\") AS \"99th-percentile\", max(\"node_get_fsm_siblings_95\") AS \"95h-percentile\", max(\"node_get_fsm_siblings_100\") AS \"100th-percentile\" FROM \":db:\".\":rp:\".\"riak\"", - "groupbys": [ - "\"nodename\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "eefbdfec-8578-46a8-a0d5-1247d1d4cf97", - "name": "Riak – Latency (ms)", - "queries": [ - { - "query": "SELECT max(\"node_put_fsm_time_median\") / 1000 AS \"median_put_milliseconds\", max(\"node_get_fsm_time_median\") / 1000 AS \"median_get_milliseconds\" FROM \":db:\".\":rp:\".\"riak\"", - "groupbys": [ - "\"nodename\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "48f268ae-3218-4b07-a2e9-575f89e2d6c9", - "name": "Riak – Reads and Writes/Minute", - "queries": [ - { - "query": "SELECT max(\"node_puts\") AS \"puts_per_minute\", max(\"node_gets\") AS \"gets_per_minute\" FROM \":db:\".\":rp:\".\"riak\"", - "groupbys": [ - "\"nodename\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "f7c601c2-1007-49ec-bbcd-3f3e678ba781", - "name": "Riak – Active Connections", - "queries": [ - { - "query": "SELECT max(\"pbc_active\") AS \"active_protobuf_connections\" FROM \":db:\".\":rp:\".\"riak\"", - "groupbys": [ - "\"nodename\"" - ], - "wheres": [] - } - ] - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "f29575f9-6b78-465c-b055-b518d6eda09d", - "name": "Riak – Read Repairs/Minute", - "queries": [ - { - "query": "SELECT max(\"read_repairs\") AS \"read_repairs_per_minute\" FROM \":db:\".\":rp:\".\"riak\"", - "groupbys": [ - "\"nodename\"" - ], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/uuid.sh b/chronograf/canned/uuid.sh deleted file mode 100755 index 0bb54464e0a..00000000000 --- a/chronograf/canned/uuid.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -uuidgen | tr A-Z a-z diff --git a/chronograf/canned/varnish.json b/chronograf/canned/varnish.json deleted file mode 100644 index a81c81fd561..00000000000 --- a/chronograf/canned/varnish.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "id": "83c57d16-a778-43ed-8941-0f9fec3408fa", - "measurement": "varnish", - "app": "varnish", - "cells": [ - { - "x": 0, - "y": 0, - "w": 12, - "h": 4, - "i": "10b406cc-50a8-4c14-bf0e-5fe8bce1661c", - "name": "Varnish - Cache Hits/Misses", - "queries": [ - { - "query": "select non_negative_derivative(mean(cache_hit)) as hits, non_negative_derivative(mean(cache_miss)) as misses from varnish", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/win_cpu.json b/chronograf/canned/win_cpu.json deleted file mode 100644 index c6b0505f961..00000000000 --- a/chronograf/canned/win_cpu.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "188b7f23-648f-4c54-99f6-6a0e2e90a2fc", - "measurement": "win_cpu", - "app": "win_system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "6921e19a-951e-42ef-b304-2b8b661fcc81", - "name": "System - CPU Usage", - "queries": [ - { - "query": "SELECT mean(\"Percent_Processor_Time\") AS \"percent_processor_time\" FROM \":db:\".\":rp:\".\"win_cpu\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/win_mem.json b/chronograf/canned/win_mem.json deleted file mode 100644 index c14dc32cb30..00000000000 --- a/chronograf/canned/win_mem.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "cef6c954-f066-4348-9425-4132429fe817", - "measurement": "win_mem", - "app": "win_system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "1c275ca5-84a7-4146-9cf0-8ed654abb627", - "name": "System - Available Bytes", - "queries": [ - { - "query": "SELECT mean(\"Available_Bytes\") AS \"available_bytes\" FROM \":db:\".\":rp:\".\"win_mem\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/win_net.json b/chronograf/canned/win_net.json deleted file mode 100644 index 7d378b6f278..00000000000 --- a/chronograf/canned/win_net.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "id": "d795c66f-0d8a-4fc0-b7bf-2cef1d2f4519", - "measurement": "win_net", - "app": "win_system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "3bf8c678-5904-46e7-9c9f-d0d16f0c3fc4", - "name": "System - TX Bytes/Second", - "queries": [ - { - "query": "SELECT mean(\"Bytes_Sent_persec\") AS \"bytes_sent\" FROM \":db:\".\":rp:\".\"win_net\"", - "groupbys": [], - "wheres": [] - } - ] - }, - { - "x": 4, - "y": 0, - "w": 4, - "h": 4, - "i": "46963ea2-b09b-4dcf-b08b-7cbcd8766f77", - "name": "RX Bytes/Second", - "queries": [ - { - "query": "SELECT mean(\"Bytes_Received_persec\") AS \"bytes_received\" FROM \":db:\".\":rp:\".\"win_net\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/win_system.json b/chronograf/canned/win_system.json deleted file mode 100644 index 77c9851ed25..00000000000 --- a/chronograf/canned/win_system.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "96bd0303-19b6-4f87-a0f9-2755c6178ba7", - "measurement": "win_system", - "app": "win_system", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "d959c815-16a8-4a2b-a6ea-e37af38d4e2f", - "name": "System - Load", - "queries": [ - { - "query": "SELECT mean(\"Processor_Queue_Length\") AS \"load\" FROM \":db:\".\":rp:\".\"win_system\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/canned/win_websvc.json b/chronograf/canned/win_websvc.json deleted file mode 100644 index b8dfa743002..00000000000 --- a/chronograf/canned/win_websvc.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "id": "c7644755-505d-46f0-b278-5c29268293b2", - "measurement": "win_websvc", - "app": "iis", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "3539e3c3-ac15-49d3-9de8-64cd514588ca", - "name": "IIS - Service", - "queries": [ - { - "query": "SELECT mean(\"Get_Requests_persec\") AS \"gets\" FROM \":db:\".\":rp:\".\"win_websvc\"", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT mean(\"Post_Requests_persec\") AS \"posts\" FROM \":db:\".\":rp:\".\"win_websvc\"", - "groupbys": [], - "wheres": [] - }, - { - "query": "SELECT mean(\"Current_Connections\") AS \"connections\" FROM \":db:\".\":rp:\".\"win_websvc\"", - "groupbys": [], - "wheres": [] - } - ] - } - ] -} diff --git a/chronograf/cmd/chronoctl/add.go b/chronograf/cmd/chronoctl/add.go deleted file mode 100644 index ccb5add7111..00000000000 --- a/chronograf/cmd/chronoctl/add.go +++ /dev/null @@ -1,120 +0,0 @@ -package main - -import ( - "context" - "strings" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -type AddCommand struct { - BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"` - ID *uint64 `short:"i" long:"id" description:"Users ID. Must be id for existing user"` - Username string `short:"n" long:"name" description:"Users name. Must be Oauth-able email address or username"` - Provider string `short:"p" long:"provider" description:"Name of the Auth provider (e.g. google, github, auth0, or generic)"` - Scheme string `short:"s" long:"scheme" description:"Authentication scheme that matches auth provider (e.g. oauth2)" default:"oauth2"` - Organizations string `short:"o" long:"orgs" description:"A comma separated list of organizations that the user should be added to" default:"default"` -} - -var addCommand AddCommand - -func (l *AddCommand) Execute(args []string) error { - c, err := NewBoltClient(l.BoltPath) - if err != nil { - return err - } - defer c.Close() - - q := chronograf.UserQuery{ - Name: &l.Username, - Provider: &l.Provider, - Scheme: &l.Scheme, - } - - if l.ID != nil { - q.ID = l.ID - } - - ctx := context.Background() - - user, err := c.UsersStore.Get(ctx, q) - if err != nil && err != chronograf.ErrUserNotFound { - return err - } else if err == chronograf.ErrUserNotFound { - user = &chronograf.User{ - Name: l.Username, - Provider: l.Provider, - Scheme: l.Scheme, - Roles: []chronograf.Role{ - { - Name: "member", - Organization: "default", - }, - }, - SuperAdmin: true, - } - - user, err = c.UsersStore.Add(ctx, user) - if err != nil { - return err - } - } else { - user.SuperAdmin = true - if len(user.Roles) == 0 { - user.Roles = []chronograf.Role{ - { - Name: "member", - Organization: "default", - }, - } - } - if err = c.UsersStore.Update(ctx, user); err != nil { - return err - } - } - - // TODO(desa): Apply mapping to user and update their roles - roles := []chronograf.Role{} -OrgLoop: - for _, org := range strings.Split(l.Organizations, ",") { - // Check to see is user is already a part of the organization - for _, r := range user.Roles { - if r.Organization == org { - continue OrgLoop - } - } - - orgQuery := chronograf.OrganizationQuery{ - ID: &org, - } - o, err := c.OrganizationsStore.Get(ctx, orgQuery) - if err != nil { - return err - } - - role := chronograf.Role{ - Organization: org, - Name: o.DefaultRole, - } - roles = append(roles, role) - } - - user.Roles = append(user.Roles, roles...) - if err = c.UsersStore.Update(ctx, user); err != nil { - return err - } - - w := NewTabWriter() - WriteHeaders(w) - WriteUser(w, user) - w.Flush() - - return nil -} - -func init() { - parser.AddCommand("add-superadmin", - "Creates a new superadmin user", - "The add-user command will create a new user with superadmin status", - &addCommand) -} diff --git a/chronograf/cmd/chronoctl/list.go b/chronograf/cmd/chronoctl/list.go deleted file mode 100644 index 6396359adfc..00000000000 --- a/chronograf/cmd/chronoctl/list.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "context" -) - -type ListCommand struct { - BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"` -} - -var listCommand ListCommand - -func (l *ListCommand) Execute(args []string) error { - c, err := NewBoltClient(l.BoltPath) - if err != nil { - return err - } - defer c.Close() - - ctx := context.Background() - users, err := c.UsersStore.All(ctx) - if err != nil { - return err - } - - w := NewTabWriter() - WriteHeaders(w) - for _, user := range users { - WriteUser(w, &user) - } - w.Flush() - - return nil -} - -func init() { - parser.AddCommand("list-users", - "Lists users", - "The list-users command will list all users in the chronograf boltdb instance", - &listCommand) -} diff --git a/chronograf/cmd/chronoctl/main.go b/chronograf/cmd/chronoctl/main.go deleted file mode 100644 index 3e8f180b96c..00000000000 --- a/chronograf/cmd/chronoctl/main.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/jessevdk/go-flags" -) - -type Options struct { -} - -var options Options - -var parser = flags.NewParser(&options, flags.Default) - -func main() { - if _, err := parser.Parse(); err != nil { - if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp { - os.Exit(0) - } else { - fmt.Fprintln(os.Stdout) - parser.WriteHelp(os.Stdout) - os.Exit(1) - } - } -} diff --git a/chronograf/cmd/chronoctl/util.go b/chronograf/cmd/chronoctl/util.go deleted file mode 100644 index fdf691681cb..00000000000 --- a/chronograf/cmd/chronoctl/util.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "os" - "strings" - "text/tabwriter" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -func NewBoltClient(path string) (*bolt.Client, error) { - c := bolt.NewClient() - c.Path = path - - ctx := context.Background() - logger := mocks.NewLogger() - var bi chronograf.BuildInfo - if err := c.Open(ctx, logger, bi); err != nil { - return nil, err - } - - return c, nil -} - -func NewTabWriter() *tabwriter.Writer { - return tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) -} - -func WriteHeaders(w io.Writer) { - fmt.Fprintln(w, "ID\tName\tProvider\tScheme\tSuperAdmin\tOrganization(s)") -} - -func WriteUser(w io.Writer, user *chronograf.User) { - orgs := []string{} - for _, role := range user.Roles { - orgs = append(orgs, role.Organization) - } - fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%t\t%s\n", user.ID, user.Name, user.Provider, user.Scheme, user.SuperAdmin, strings.Join(orgs, ",")) -} diff --git a/chronograf/cmd/chronograf/main.go b/chronograf/cmd/chronograf/main.go deleted file mode 100644 index 516dc2f4925..00000000000 --- a/chronograf/cmd/chronograf/main.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "context" - "log" - "os" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/server" - flags "github.com/jessevdk/go-flags" -) - -// Build flags -var ( - version = "" - commit = "" -) - -func main() { - srv := server.Server{ - BuildInfo: chronograf.BuildInfo{ - Version: version, - Commit: commit, - }, - } - - parser := flags.NewParser(&srv, flags.Default) - parser.ShortDescription = `Chronograf` - parser.LongDescription = `Options for Chronograf` - - if _, err := parser.Parse(); err != nil { - code := 1 - if fe, ok := err.(*flags.Error); ok { - if fe.Type == flags.ErrHelp { - code = 0 - } - } - os.Exit(code) - } - - if srv.ShowVersion { - log.Printf("Chronograf %s (git: %s)\n", version, commit) - os.Exit(0) - } - - ctx := context.Background() - if err := srv.Serve(ctx); err != nil { - log.Fatalln(err) - } -} diff --git a/chronograf/dist/Makefile b/chronograf/dist/Makefile deleted file mode 100644 index 0f7175c065a..00000000000 --- a/chronograf/dist/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# List any generated files here -TARGETS = dist_gen.go -# List any source files used to generate the targets here -SOURCES = dist.go $(shell find ../../ui/build -type f) -# List any directories that have their own Makefile here -SUBDIRS = - -# Default target -all: $(SUBDIRS) $(TARGETS) - -# Recurse into subdirs for same make goal -$(SUBDIRS): - $(MAKE) -C $@ $(MAKECMDGOALS) - -# Clean all targets recursively -clean: $(SUBDIRS) - rm -f $(TARGETS) - -# Define go generate if not already defined -GO_GENERATE := go generate - -# Run go generate for the targets -$(TARGETS): $(SOURCES) - $(GO_GENERATE) -x - -.PHONY: all clean $(SUBDIRS) diff --git a/chronograf/dist/dir.go b/chronograf/dist/dir.go deleted file mode 100644 index 1f4ac90b933..00000000000 --- a/chronograf/dist/dir.go +++ /dev/null @@ -1,33 +0,0 @@ -package dist - -import ( - "net/http" - "os" -) - -// Dir functions like http.Dir except returns the content of a default file if not found. -type Dir struct { - Default string - dir http.Dir -} - -// NewDir constructs a Dir with a default file -func NewDir(dir, def string) Dir { - return Dir{ - Default: def, - dir: http.Dir(dir), - } -} - -// Open will return the file in the dir if it exists, or, the Default file otherwise. -func (d Dir) Open(name string) (http.File, error) { - f, err := d.dir.Open(name) - if err != nil { - f, err = os.Open(d.Default) - if err != nil { - return nil, err - } - return f, nil - } - return f, err -} diff --git a/chronograf/dist/dist.go b/chronograf/dist/dist.go deleted file mode 100644 index 0199d24a5f6..00000000000 --- a/chronograf/dist/dist.go +++ /dev/null @@ -1,88 +0,0 @@ -package dist - -//go:generate env GO111MODULE=on go run github.com/kevinburke/go-bindata/go-bindata -o dist_gen.go -ignore 'map|go' -tags assets -pkg dist ../../ui/build/... - -import ( - "fmt" - "net/http" - - "github.com/elazarl/go-bindata-assetfs" -) - -// DebugAssets serves assets via a specified directory -type DebugAssets struct { - Dir string // Dir is a directory location of asset files - Default string // Default is the file to serve if file is not found. -} - -// Handler is an http.FileServer for the Dir -func (d *DebugAssets) Handler() http.Handler { - return http.FileServer(NewDir(d.Dir, d.Default)) -} - -// BindataAssets serves assets from go-bindata, but, also serves Default if assent doesn't exist -// This is to support single-page react-apps with its own router. -type BindataAssets struct { - Prefix string // Prefix is prepended to the http file request - Default string // Default is the file to serve if the file is not found - DefaultContentType string // DefaultContentType is the content type of the default file -} - -// Handler serves go-bindata using a go-bindata-assetfs façade -func (b *BindataAssets) Handler() http.Handler { - return b -} - -// addCacheHeaders requests an hour of Cache-Control and sets an ETag based on file size and modtime -func (b *BindataAssets) addCacheHeaders(filename string, w http.ResponseWriter) error { - w.Header().Add("Cache-Control", "public, max-age=3600") - fi, err := AssetInfo(filename) - if err != nil { - return err - } - - hour, minute, second := fi.ModTime().Clock() - etag := fmt.Sprintf(`"%d%d%d%d%d"`, fi.Size(), fi.ModTime().Day(), hour, minute, second) - - w.Header().Set("ETag", etag) - return nil -} - -// ServeHTTP wraps http.FileServer by returning a default asset if the asset -// doesn't exist. This supports single-page react-apps with its own -// built-in router. Additionally, we override the content-type if the -// Default file is used. -func (b *BindataAssets) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // def wraps the assets to return the default file if the file doesn't exist - def := func(name string) ([]byte, error) { - // If the named asset exists, then return it directly. - octets, err := Asset(name) - if err != nil { - // If this is at / then we just error out so we can return a Directory - // This directory will then be redirected by go to the /index.html - if name == b.Prefix { - return nil, err - } - // If this is anything other than slash, we just return the default - // asset. This default asset will handle the routing. - // Additionally, because we know we are returning the default asset, - // we need to set the default asset's content-type. - w.Header().Set("Content-Type", b.DefaultContentType) - if err := b.addCacheHeaders(b.Default, w); err != nil { - return nil, err - } - return Asset(b.Default) - } - if err := b.addCacheHeaders(name, w); err != nil { - return nil, err - } - return octets, nil - } - var dir http.FileSystem = &assetfs.AssetFS{ - Asset: def, - AssetDir: AssetDir, - AssetInfo: AssetInfo, - Prefix: b.Prefix, - } - http.FileServer(dir).ServeHTTP(w, r) -} diff --git a/chronograf/enterprise/enterprise.go b/chronograf/enterprise/enterprise.go deleted file mode 100644 index fd287bc6030..00000000000 --- a/chronograf/enterprise/enterprise.go +++ /dev/null @@ -1,225 +0,0 @@ -package enterprise - -import ( - "container/ring" - "net/url" - "strings" - - "context" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/influx" -) - -var _ chronograf.TimeSeries = &Client{} - -// Ctrl represents administrative controls over an Influx Enterprise cluster -type Ctrl interface { - ShowCluster(ctx context.Context) (*Cluster, error) - - Users(ctx context.Context, name *string) (*Users, error) - User(ctx context.Context, name string) (*User, error) - CreateUser(ctx context.Context, name, passwd string) error - DeleteUser(ctx context.Context, name string) error - ChangePassword(ctx context.Context, name, passwd string) error - SetUserPerms(ctx context.Context, name string, perms Permissions) error - - UserRoles(ctx context.Context) (map[string]Roles, error) - - Roles(ctx context.Context, name *string) (*Roles, error) - Role(ctx context.Context, name string) (*Role, error) - CreateRole(ctx context.Context, name string) error - DeleteRole(ctx context.Context, name string) error - SetRolePerms(ctx context.Context, name string, perms Permissions) error - SetRoleUsers(ctx context.Context, name string, users []string) error - AddRoleUsers(ctx context.Context, name string, users []string) error - RemoveRoleUsers(ctx context.Context, name string, users []string) error -} - -// Client is a device for retrieving time series data from an Influx Enterprise -// cluster. It is configured using the addresses of one or more meta node URLs. -// Data node URLs are retrieved automatically from the meta nodes and queries -// are appropriately load balanced across the cluster. -type Client struct { - Ctrl - UsersStore chronograf.UsersStore - RolesStore chronograf.RolesStore - Logger chronograf.Logger - - dataNodes *ring.Ring - opened bool -} - -// NewClientWithTimeSeries initializes a Client with a known set of TimeSeries. -func NewClientWithTimeSeries(lg chronograf.Logger, mu string, authorizer influx.Authorizer, tls, insecure bool, series ...chronograf.TimeSeries) (*Client, error) { - metaURL, err := parseMetaURL(mu, tls) - if err != nil { - return nil, err - } - - ctrl := NewMetaClient(metaURL, insecure, authorizer) - c := &Client{ - Ctrl: ctrl, - UsersStore: &UserStore{ - Ctrl: ctrl, - Logger: lg, - }, - RolesStore: &RolesStore{ - Ctrl: ctrl, - Logger: lg, - }, - } - - c.dataNodes = ring.New(len(series)) - - for _, s := range series { - c.dataNodes.Value = s - c.dataNodes = c.dataNodes.Next() - } - - return c, nil -} - -// NewClientWithURL initializes an Enterprise client with a URL to a Meta Node. -// Acceptable URLs include host:port combinations as well as scheme://host:port -// varieties. TLS is used when the URL contains "https" or when the TLS -// parameter is set. authorizer will add the correct `Authorization` headers -// on the out-bound request. -func NewClientWithURL(mu string, authorizer influx.Authorizer, tls bool, insecure bool, lg chronograf.Logger) (*Client, error) { - metaURL, err := parseMetaURL(mu, tls) - if err != nil { - return nil, err - } - - ctrl := NewMetaClient(metaURL, insecure, authorizer) - return &Client{ - Ctrl: ctrl, - UsersStore: &UserStore{ - Ctrl: ctrl, - Logger: lg, - }, - RolesStore: &RolesStore{ - Ctrl: ctrl, - Logger: lg, - }, - Logger: lg, - }, nil -} - -// Connect prepares a Client to process queries. It must be called prior to calling Query -func (c *Client) Connect(ctx context.Context, src *chronograf.Source) error { - c.opened = true - // return early if we already have dataNodes - if c.dataNodes != nil { - return nil - } - cluster, err := c.Ctrl.ShowCluster(ctx) - if err != nil { - return err - } - - c.dataNodes = ring.New(len(cluster.DataNodes)) - for _, dn := range cluster.DataNodes { - cl := &influx.Client{ - Logger: c.Logger, - } - dataSrc := &chronograf.Source{} - *dataSrc = *src - dataSrc.URL = dn.HTTPAddr - if err := cl.Connect(ctx, dataSrc); err != nil { - continue - } - c.dataNodes.Value = cl - c.dataNodes = c.dataNodes.Next() - } - return nil -} - -// Query retrieves timeseries information pertaining to a specified query. It -// can be cancelled by using a provided context. -func (c *Client) Query(ctx context.Context, q chronograf.Query) (chronograf.Response, error) { - if !c.opened { - return nil, chronograf.ErrUninitialized - } - return c.nextDataNode().Query(ctx, q) -} - -// Write records points into a time series -func (c *Client) Write(ctx context.Context, points []chronograf.Point) error { - if !c.opened { - return chronograf.ErrUninitialized - } - return c.nextDataNode().Write(ctx, points) -} - -// Users is the interface to the users within Influx Enterprise -func (c *Client) Users(context.Context) chronograf.UsersStore { - return c.UsersStore -} - -// Roles provide a grouping of permissions given to a grouping of users -func (c *Client) Roles(ctx context.Context) (chronograf.RolesStore, error) { - return c.RolesStore, nil -} - -// Permissions returns all Influx Enterprise permission strings -func (c *Client) Permissions(context.Context) chronograf.Permissions { - all := chronograf.Allowances{ - "NoPermissions", - "ViewAdmin", - "ViewChronograf", - "CreateDatabase", - "CreateUserAndRole", - "AddRemoveNode", - "DropDatabase", - "DropData", - "ReadData", - "WriteData", - "Rebalance", - "ManageShard", - "ManageContinuousQuery", - "ManageQuery", - "ManageSubscription", - "Monitor", - "CopyShard", - "KapacitorAPI", - "KapacitorConfigAPI", - } - - return chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: all, - }, - { - Scope: chronograf.DBScope, - Allowed: all, - }, - } -} - -// nextDataNode retrieves the next available data node -func (c *Client) nextDataNode() chronograf.TimeSeries { - c.dataNodes = c.dataNodes.Next() - return c.dataNodes.Value.(chronograf.TimeSeries) -} - -// parseMetaURL constructs a url from either a host:port combination or a -// scheme://host:port combo. The optional TLS parameter takes precedence over -// any TLS preference found in the provided URL -func parseMetaURL(mu string, tls bool) (metaURL *url.URL, err error) { - if strings.Contains(mu, "http") { - metaURL, err = url.Parse(mu) - } else { - metaURL = &url.URL{ - Scheme: "http", - Host: mu, - } - } - - if tls { - metaURL.Scheme = "https" - } - - return -} diff --git a/chronograf/enterprise/enterprise_test.go b/chronograf/enterprise/enterprise_test.go deleted file mode 100644 index 06efffc9fcf..00000000000 --- a/chronograf/enterprise/enterprise_test.go +++ /dev/null @@ -1,269 +0,0 @@ -package enterprise_test - -import ( - "context" - "net/http" - "net/http/httptest" - "reflect" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/enterprise" - "github.com/influxdata/influxdb/v2/chronograf/influx" -) - -func Test_Enterprise_FetchesDataNodes(t *testing.T) { - t.Parallel() - showClustersCalled := false - ctrl := &mockCtrl{ - showCluster: func(ctx context.Context) (*enterprise.Cluster, error) { - showClustersCalled = true - return &enterprise.Cluster{}, nil - }, - } - - cl := &enterprise.Client{ - Ctrl: ctrl, - } - - bg := context.Background() - err := cl.Connect(bg, &chronograf.Source{}) - - if err != nil { - t.Fatal("Unexpected error while creating enterprise client. err:", err) - } - - if !showClustersCalled { - t.Fatal("Expected request to meta node but none was issued") - } -} - -func Test_Enterprise_IssuesQueries(t *testing.T) { - t.Parallel() - - called := false - ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - called = true - if r.URL.Path != "/query" { - t.Fatal("Expected request to '/query' but was", r.URL.Path) - } - rw.Write([]byte(`{}`)) - })) - defer ts.Close() - - cl := &enterprise.Client{ - Ctrl: NewMockControlClient(ts.URL), - Logger: &chronograf.NoopLogger{}, - } - - err := cl.Connect(context.Background(), &chronograf.Source{}) - if err != nil { - t.Fatal("Unexpected error initializing client: err:", err) - } - - _, err = cl.Query(context.Background(), chronograf.Query{Command: "show shards", DB: "_internal", RP: "autogen"}) - - if err != nil { - t.Fatal("Unexpected error while querying data node: err:", err) - } - - if !called { - t.Fatal("Expected request to data node but none was received") - } -} - -func Test_Enterprise_AdvancesDataNodes(t *testing.T) { - m1 := NewMockTimeSeries("http://host-1.example.com:8086") - m2 := NewMockTimeSeries("http://host-2.example.com:8086") - cl, err := enterprise.NewClientWithTimeSeries( - &chronograf.NoopLogger{}, - "http://meta.example.com:8091", - &influx.BasicAuth{ - Username: "marty", - Password: "thelake", - }, - false, - false, - chronograf.TimeSeries(m1), - chronograf.TimeSeries(m2)) - if err != nil { - t.Error("Unexpected error while initializing client: err:", err) - } - - err = cl.Connect(context.Background(), &chronograf.Source{}) - if err != nil { - t.Error("Unexpected error while initializing client: err:", err) - } - - _, err = cl.Query(context.Background(), chronograf.Query{Command: "show shards", DB: "_internal", RP: "autogen"}) - if err != nil { - t.Fatal("Unexpected error while issuing query: err:", err) - } - - _, err = cl.Query(context.Background(), chronograf.Query{Command: "show shards", DB: "_internal", RP: "autogen"}) - if err != nil { - t.Fatal("Unexpected error while issuing query: err:", err) - } - - if m1.QueryCtr != 1 || m2.QueryCtr != 1 { - t.Fatalf("Expected m1.Query to be called once but was %d. Expected m2.Query to be called once but was %d\n", m1.QueryCtr, m2.QueryCtr) - } -} - -func Test_Enterprise_NewClientWithURL(t *testing.T) { - t.Parallel() - - urls := []struct { - name string - url string - username string - password string - tls bool - insecureSkipVerify bool - wantErr bool - }{ - { - name: "no tls should have no error", - url: "http://localhost:8086", - }, - { - name: "tls should have no error", - url: "https://localhost:8086", - }, - { - name: "no tls but with basic auth", - url: "http://localhost:8086", - username: "username", - password: "password", - }, - { - name: "tls request but url is not tls should not error", - url: "http://localhost:8086", - tls: true, - }, - { - name: "https with tls and with insecureSkipVerify should not error", - url: "https://localhost:8086", - tls: true, - insecureSkipVerify: true, - }, - { - name: "URL does not require http or https", - url: "localhost:8086", - }, - { - name: "URL with TLS request should not error", - url: "localhost:8086", - tls: true, - }, - { - name: "invalid URL causes error", - url: ":http", - wantErr: true, - }, - } - - for _, testURL := range urls { - _, err := enterprise.NewClientWithURL( - testURL.url, - &influx.BasicAuth{ - Username: testURL.username, - Password: testURL.password, - }, - testURL.tls, - testURL.insecureSkipVerify, - &chronograf.NoopLogger{}) - if err != nil && !testURL.wantErr { - t.Errorf("Unexpected error creating Client with URL %s and TLS preference %t. err: %s", testURL.url, testURL.tls, err.Error()) - } else if err == nil && testURL.wantErr { - t.Errorf("Expected error creating Client with URL %s and TLS preference %t", testURL.url, testURL.tls) - } - } -} - -func Test_Enterprise_ComplainsIfNotOpened(t *testing.T) { - m1 := NewMockTimeSeries("http://host-1.example.com:8086") - cl, err := enterprise.NewClientWithTimeSeries( - &chronograf.NoopLogger{}, - "http://meta.example.com:8091", - &influx.BasicAuth{ - Username: "docbrown", - Password: "1.21 gigawatts", - }, - false, false, chronograf.TimeSeries(m1)) - if err != nil { - t.Error("Expected nil, but was this err:", err) - } - _, err = cl.Query(context.Background(), chronograf.Query{Command: "show shards", DB: "_internal", RP: "autogen"}) - if err != chronograf.ErrUninitialized { - t.Error("Expected ErrUninitialized, but was this err:", err) - } -} - -func TestClient_Permissions(t *testing.T) { - tests := []struct { - name string - - want chronograf.Permissions - }{ - { - name: "All possible enterprise permissions", - want: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{ - "NoPermissions", - "ViewAdmin", - "ViewChronograf", - "CreateDatabase", - "CreateUserAndRole", - "AddRemoveNode", - "DropDatabase", - "DropData", - "ReadData", - "WriteData", - "Rebalance", - "ManageShard", - "ManageContinuousQuery", - "ManageQuery", - "ManageSubscription", - "Monitor", - "CopyShard", - "KapacitorAPI", - "KapacitorConfigAPI", - }, - }, - { - Scope: chronograf.DBScope, - Allowed: chronograf.Allowances{ - "NoPermissions", - "ViewAdmin", - "ViewChronograf", - "CreateDatabase", - "CreateUserAndRole", - "AddRemoveNode", - "DropDatabase", - "DropData", - "ReadData", - "WriteData", - "Rebalance", - "ManageShard", - "ManageContinuousQuery", - "ManageQuery", - "ManageSubscription", - "Monitor", - "CopyShard", - "KapacitorAPI", - "KapacitorConfigAPI", - }, - }, - }, - }, - } - for _, tt := range tests { - c := &enterprise.Client{} - if got := c.Permissions(context.Background()); !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. Client.Permissions() = %v, want %v", tt.name, got, tt.want) - } - } -} diff --git a/chronograf/enterprise/meta.go b/chronograf/enterprise/meta.go deleted file mode 100644 index 7b1bcd11f39..00000000000 --- a/chronograf/enterprise/meta.go +++ /dev/null @@ -1,568 +0,0 @@ -package enterprise - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/influx" -) - -// Shared transports for all clients to prevent leaking connections -var ( - skipVerifyTransport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - defaultTransport = &http.Transport{} -) - -type client interface { - Do(URL *url.URL, path, method string, authorizer influx.Authorizer, params map[string]string, body io.Reader) (*http.Response, error) -} - -// MetaClient represents a Meta node in an Influx Enterprise cluster -type MetaClient struct { - URL *url.URL - client client - authorizer influx.Authorizer -} - -// NewMetaClient represents a meta node in an Influx Enterprise cluster -func NewMetaClient(url *url.URL, InsecureSkipVerify bool, authorizer influx.Authorizer) *MetaClient { - return &MetaClient{ - URL: url, - client: &defaultClient{ - InsecureSkipVerify: InsecureSkipVerify, - }, - authorizer: authorizer, - } -} - -type jsonLDAPConfig struct { - Enabled bool `json:"enabled"` -} - -// LDAPConfig represents the configuration for ldap from influxdb -type LDAPConfig struct { - Structured jsonLDAPConfig `json:"structured"` -} - -func (m *MetaClient) requestLDAPChannel(ctx context.Context, errors chan error) chan *http.Response { - channel := make(chan *http.Response, 1) - go (func() { - res, err := m.Do(ctx, "/ldap/v1/config", "GET", m.authorizer, nil, nil) - if err != nil { - errors <- err - } else { - channel <- res - } - })() - - return channel -} - -// GetLDAPConfig get the current ldap config response from influxdb enterprise -func (m *MetaClient) GetLDAPConfig(ctx context.Context) (*LDAPConfig, error) { - ctxt, cancel := context.WithTimeout(ctx, 2*time.Second) - defer cancel() - - errorCh := make(chan error, 1) - responseChannel := m.requestLDAPChannel(ctxt, errorCh) - - select { - case res := <-responseChannel: - result, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - - var config LDAPConfig - err = json.Unmarshal(result, &config) - if err != nil { - return nil, err - } - - return &config, nil - case err := <-errorCh: - return nil, err - case <-ctxt.Done(): - return nil, ctxt.Err() - } -} - -// ShowCluster returns the cluster configuration (not health) -func (m *MetaClient) ShowCluster(ctx context.Context) (*Cluster, error) { - res, err := m.Do(ctx, "/show-cluster", "GET", m.authorizer, nil, nil) - if err != nil { - return nil, err - } - - defer res.Body.Close() - dec := json.NewDecoder(res.Body) - out := &Cluster{} - err = dec.Decode(out) - if err != nil { - return nil, err - } - return out, nil -} - -// Users gets all the users. If name is not nil it filters for a single user -func (m *MetaClient) Users(ctx context.Context, name *string) (*Users, error) { - params := map[string]string{} - if name != nil { - params["name"] = *name - } - res, err := m.Do(ctx, "/user", "GET", m.authorizer, params, nil) - if err != nil { - return nil, err - } - - defer res.Body.Close() - dec := json.NewDecoder(res.Body) - users := &Users{} - err = dec.Decode(users) - if err != nil { - return nil, err - } - return users, nil -} - -// User returns a single Influx Enterprise user -func (m *MetaClient) User(ctx context.Context, name string) (*User, error) { - users, err := m.Users(ctx, &name) - if err != nil { - return nil, err - } - - for _, user := range users.Users { - return &user, nil - } - return nil, fmt.Errorf("no user found") -} - -// CreateUser adds a user to Influx Enterprise -func (m *MetaClient) CreateUser(ctx context.Context, name, passwd string) error { - return m.CreateUpdateUser(ctx, "create", name, passwd) -} - -// ChangePassword updates a user's password in Influx Enterprise -func (m *MetaClient) ChangePassword(ctx context.Context, name, passwd string) error { - return m.CreateUpdateUser(ctx, "change-password", name, passwd) -} - -// CreateUpdateUser is a helper function to POST to the /user Influx Enterprise endpoint -func (m *MetaClient) CreateUpdateUser(ctx context.Context, action, name, passwd string) error { - a := &UserAction{ - Action: action, - User: &User{ - Name: name, - Password: passwd, - }, - } - return m.Post(ctx, "/user", a, nil) -} - -// DeleteUser removes a user from Influx Enterprise -func (m *MetaClient) DeleteUser(ctx context.Context, name string) error { - a := &UserAction{ - Action: "delete", - User: &User{ - Name: name, - }, - } - - return m.Post(ctx, "/user", a, nil) -} - -// RemoveUserPerms revokes permissions for a user in Influx Enterprise -func (m *MetaClient) RemoveUserPerms(ctx context.Context, name string, perms Permissions) error { - a := &UserAction{ - Action: "remove-permissions", - User: &User{ - Name: name, - Permissions: perms, - }, - } - return m.Post(ctx, "/user", a, nil) -} - -// SetUserPerms removes permissions not in set and then adds the requested perms -func (m *MetaClient) SetUserPerms(ctx context.Context, name string, perms Permissions) error { - user, err := m.User(ctx, name) - if err != nil { - return err - } - - revoke, add := permissionsDifference(perms, user.Permissions) - - // first, revoke all the permissions the user currently has, but, - // shouldn't... - if len(revoke) > 0 { - err := m.RemoveUserPerms(ctx, name, revoke) - if err != nil { - return err - } - } - - // ... next, add any permissions the user should have - if len(add) > 0 { - a := &UserAction{ - Action: "add-permissions", - User: &User{ - Name: name, - Permissions: add, - }, - } - return m.Post(ctx, "/user", a, nil) - } - return nil -} - -// UserRoles returns a map of users to all of their current roles -func (m *MetaClient) UserRoles(ctx context.Context) (map[string]Roles, error) { - res, err := m.Roles(ctx, nil) - if err != nil { - return nil, err - } - - userRoles := make(map[string]Roles) - for _, role := range res.Roles { - for _, u := range role.Users { - ur, ok := userRoles[u] - if !ok { - ur = Roles{} - } - ur.Roles = append(ur.Roles, role) - userRoles[u] = ur - } - } - return userRoles, nil -} - -// Roles gets all the roles. If name is not nil it filters for a single role -func (m *MetaClient) Roles(ctx context.Context, name *string) (*Roles, error) { - params := map[string]string{} - if name != nil { - params["name"] = *name - } - res, err := m.Do(ctx, "/role", "GET", m.authorizer, params, nil) - if err != nil { - return nil, err - } - - defer res.Body.Close() - dec := json.NewDecoder(res.Body) - roles := &Roles{} - err = dec.Decode(roles) - if err != nil { - return nil, err - } - return roles, nil -} - -// Role returns a single named role -func (m *MetaClient) Role(ctx context.Context, name string) (*Role, error) { - roles, err := m.Roles(ctx, &name) - if err != nil { - return nil, err - } - for _, role := range roles.Roles { - return &role, nil - } - return nil, fmt.Errorf("no role found") -} - -// CreateRole adds a role to Influx Enterprise -func (m *MetaClient) CreateRole(ctx context.Context, name string) error { - a := &RoleAction{ - Action: "create", - Role: &Role{ - Name: name, - }, - } - return m.Post(ctx, "/role", a, nil) -} - -// DeleteRole removes a role from Influx Enterprise -func (m *MetaClient) DeleteRole(ctx context.Context, name string) error { - a := &RoleAction{ - Action: "delete", - Role: &Role{ - Name: name, - }, - } - return m.Post(ctx, "/role", a, nil) -} - -// RemoveRolePerms revokes permissions from a role -func (m *MetaClient) RemoveRolePerms(ctx context.Context, name string, perms Permissions) error { - a := &RoleAction{ - Action: "remove-permissions", - Role: &Role{ - Name: name, - Permissions: perms, - }, - } - return m.Post(ctx, "/role", a, nil) -} - -// SetRolePerms removes permissions not in set and then adds the requested perms to role -func (m *MetaClient) SetRolePerms(ctx context.Context, name string, perms Permissions) error { - role, err := m.Role(ctx, name) - if err != nil { - return err - } - - revoke, add := permissionsDifference(perms, role.Permissions) - - // first, revoke all the permissions the role currently has, but, - // shouldn't... - if len(revoke) > 0 { - err := m.RemoveRolePerms(ctx, name, revoke) - if err != nil { - return err - } - } - - // ... next, add any permissions the role should have - if len(add) > 0 { - a := &RoleAction{ - Action: "add-permissions", - Role: &Role{ - Name: name, - Permissions: add, - }, - } - return m.Post(ctx, "/role", a, nil) - } - return nil -} - -// SetRoleUsers removes users not in role and then adds the requested users to role -func (m *MetaClient) SetRoleUsers(ctx context.Context, name string, users []string) error { - role, err := m.Role(ctx, name) - if err != nil { - return err - } - revoke, add := Difference(users, role.Users) - if err := m.RemoveRoleUsers(ctx, name, revoke); err != nil { - return err - } - - return m.AddRoleUsers(ctx, name, add) -} - -// Difference compares two sets and returns a set to be removed and a set to be added -func Difference(wants []string, haves []string) (revoke []string, add []string) { - for _, want := range wants { - found := false - for _, got := range haves { - if want != got { - continue - } - found = true - } - if !found { - add = append(add, want) - } - } - for _, got := range haves { - found := false - for _, want := range wants { - if want != got { - continue - } - found = true - break - } - if !found { - revoke = append(revoke, got) - } - } - return -} - -func permissionsDifference(wants Permissions, haves Permissions) (revoke Permissions, add Permissions) { - revoke = make(Permissions) - add = make(Permissions) - for scope, want := range wants { - have, ok := haves[scope] - if ok { - r, a := Difference(want, have) - revoke[scope] = r - add[scope] = a - } else { - add[scope] = want - } - } - - for scope, have := range haves { - _, ok := wants[scope] - if !ok { - revoke[scope] = have - } - } - return -} - -// AddRoleUsers updates a role to have additional users. -func (m *MetaClient) AddRoleUsers(ctx context.Context, name string, users []string) error { - // No permissions to add, so, role is in the right state - if len(users) == 0 { - return nil - } - - a := &RoleAction{ - Action: "add-users", - Role: &Role{ - Name: name, - Users: users, - }, - } - return m.Post(ctx, "/role", a, nil) -} - -// RemoveRoleUsers updates a role to remove some users. -func (m *MetaClient) RemoveRoleUsers(ctx context.Context, name string, users []string) error { - // No permissions to add, so, role is in the right state - if len(users) == 0 { - return nil - } - - a := &RoleAction{ - Action: "remove-users", - Role: &Role{ - Name: name, - Users: users, - }, - } - return m.Post(ctx, "/role", a, nil) -} - -// Post is a helper function to POST to Influx Enterprise -func (m *MetaClient) Post(ctx context.Context, path string, action interface{}, params map[string]string) error { - b, err := json.Marshal(action) - if err != nil { - return err - } - body := bytes.NewReader(b) - _, err = m.Do(ctx, path, "POST", m.authorizer, params, body) - if err != nil { - return err - } - return nil -} - -type defaultClient struct { - Leader string - InsecureSkipVerify bool -} - -// Do is a helper function to interface with Influx Enterprise's Meta API -func (d *defaultClient) Do(URL *url.URL, path, method string, authorizer influx.Authorizer, params map[string]string, body io.Reader) (*http.Response, error) { - p := url.Values{} - for k, v := range params { - p.Add(k, v) - } - - URL.Path = path - URL.RawQuery = p.Encode() - if d.Leader == "" { - d.Leader = URL.Host - } else if d.Leader != URL.Host { - URL.Host = d.Leader - } - - req, err := http.NewRequest(method, URL.String(), body) - if err != nil { - return nil, err - } - - if body != nil { - req.Header.Set("Content-Type", "application/json") - } - - if authorizer != nil { - if err = authorizer.Set(req); err != nil { - return nil, err - } - } - - // Meta servers will redirect (307) to leader. We need - // special handling to preserve authentication headers. - client := &http.Client{ - CheckRedirect: d.AuthedCheckRedirect, - } - - if d.InsecureSkipVerify { - client.Transport = skipVerifyTransport - } else { - client.Transport = defaultTransport - } - - res, err := client.Do(req) - if err != nil { - return nil, err - } - - if res.StatusCode != http.StatusOK { - defer res.Body.Close() - dec := json.NewDecoder(res.Body) - out := &Error{} - err = dec.Decode(out) - if err != nil { - return nil, err - } - return nil, errors.New(out.Error) - } - - return res, nil - -} - -// AuthedCheckRedirect tries to follow the Influx Enterprise pattern of -// redirecting to the leader but preserving authentication headers. -func (d *defaultClient) AuthedCheckRedirect(req *http.Request, via []*http.Request) error { - if len(via) >= 10 { - return errors.New("too many redirects") - } else if len(via) == 0 { - return nil - } - preserve := "Authorization" - if auth, ok := via[0].Header[preserve]; ok { - req.Header[preserve] = auth - } - d.Leader = req.URL.Host - return nil -} - -// Do is a cancelable function to interface with Influx Enterprise's Meta API -func (m *MetaClient) Do(ctx context.Context, path, method string, authorizer influx.Authorizer, params map[string]string, body io.Reader) (*http.Response, error) { - type result struct { - Response *http.Response - Err error - } - - resps := make(chan (result)) - go func() { - resp, err := m.client.Do(m.URL, path, method, authorizer, params, body) - resps <- result{resp, err} - }() - - select { - case resp := <-resps: - return resp.Response, resp.Err - case <-ctx.Done(): - return nil, chronograf.ErrUpstreamTimeout - } -} diff --git a/chronograf/enterprise/meta_test.go b/chronograf/enterprise/meta_test.go deleted file mode 100644 index c82107e8563..00000000000 --- a/chronograf/enterprise/meta_test.go +++ /dev/null @@ -1,1498 +0,0 @@ -package enterprise - -import ( - "bytes" - "context" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "reflect" - "testing" - "time" - - "github.com/influxdata/influxdb/v2/chronograf/influx" -) - -func TestMetaClient_ShowCluster(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - tests := []struct { - name string - fields fields - want *Cluster - wantErr bool - }{ - { - name: "Successful Show Cluster", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"data":[{"id":2,"version":"1.1.0-c1.1.0","tcpAddr":"data-1.twinpinesmall.net:8088","httpAddr":"data-1.twinpinesmall.net:8086","httpScheme":"https","status":"joined"}],"meta":[{"id":1,"addr":"meta-0.twinpinesmall.net:8091","httpScheme":"http","tcpAddr":"meta-0.twinpinesmall.net:8089","version":"1.1.0-c1.1.0"}]}`), - nil, - nil, - ), - }, - want: &Cluster{ - DataNodes: []DataNode{ - { - ID: 2, - TCPAddr: "data-1.twinpinesmall.net:8088", - HTTPAddr: "data-1.twinpinesmall.net:8086", - HTTPScheme: "https", - Status: "joined", - }, - }, - MetaNodes: []Node{ - { - ID: 1, - Addr: "meta-0.twinpinesmall.net:8091", - HTTPScheme: "http", - TCPAddr: "meta-0.twinpinesmall.net:8089", - }, - }, - }, - }, - { - name: "Failed Show Cluster", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusBadGateway, - nil, - nil, - fmt.Errorf("time circuits on. Flux Capacitor... fluxxing"), - ), - }, - wantErr: true, - }, - { - name: "Bad JSON from Show Cluster", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{data}`), - nil, - nil, - ), - }, - wantErr: true, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - got, err := m.ShowCluster(context.Background()) - if (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.ShowCluster() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. MetaClient.ShowCluster() = %v, want %v", tt.name, got, tt.want) - } - if tt.wantErr { - continue - } - reqs := tt.fields.client.Requests - if len(reqs) != 1 { - t.Errorf("%q. MetaClient.ShowCluster() expected 1 but got %d", tt.name, len(reqs)) - continue - } - req := reqs[0] - if req.Method != "GET" { - t.Errorf("%q. MetaClient.ShowCluster() expected GET method", tt.name) - } - if req.URL.Path != "/show-cluster" { - t.Errorf("%q. MetaClient.ShowCluster() expected /show-cluster path but got %s", tt.name, req.URL.Path) - } - } -} - -func TestMetaClient_Users(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name *string - } - tests := []struct { - name string - fields fields - args args - want *Users - wantErr bool - }{ - { - name: "Successful Show users", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: nil, - }, - want: &Users{ - Users: []User{ - { - Name: "admin", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - }, - }, - }, - }, - { - name: "Successful Show users single user", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: &[]string{"admin"}[0], - }, - want: &Users{ - Users: []User{ - { - Name: "admin", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - }, - }, - }, - }, - { - name: "Failure Show users", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - fmt.Errorf("time circuits on. Flux Capacitor... fluxxing"), - ), - }, - args: args{ - ctx: context.Background(), - name: nil, - }, - wantErr: true, - }, - { - name: "Bad JSON from Show users", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{foo}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: nil, - }, - wantErr: true, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - got, err := m.Users(tt.args.ctx, tt.args.name) - if (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.Users() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. MetaClient.Users() = %v, want %v", tt.name, got, tt.want) - } - } -} - -func TestMetaClient_User(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - } - tests := []struct { - name string - fields fields - args args - want *User - wantErr bool - }{ - { - name: "Successful Show users", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - }, - want: &User{ - Name: "admin", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - }, - }, - { - name: "No such user", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusNotFound, - []byte(`{"error":"user not found"}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "unknown", - }, - wantErr: true, - }, - { - name: "Bad JSON", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusNotFound, - []byte(`{BAD}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - }, - wantErr: true, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - got, err := m.User(tt.args.ctx, tt.args.name) - if (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.User() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. MetaClient.User() = %v, want %v", tt.name, got, tt.want) - } - } -} - -func TestMetaClient_CreateUser(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - passwd string - } - tests := []struct { - name string - fields fields - args args - want string - wantErr bool - }{ - { - name: "Successful Create User", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - nil, - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - passwd: "hunter2", - }, - want: `{"action":"create","user":{"name":"admin","password":"hunter2"}}`, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - if err := m.CreateUser(tt.args.ctx, tt.args.name, tt.args.passwd); (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.CreateUser() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - if tt.wantErr { - continue - } - reqs := tt.fields.client.Requests - if len(reqs) != 1 { - t.Errorf("%q. MetaClient.CreateUser() expected 1 but got %d", tt.name, len(reqs)) - continue - } - req := reqs[0] - if req.Method != "POST" { - t.Errorf("%q. MetaClient.CreateUser() expected POST method", tt.name) - } - if req.URL.Path != "/user" { - t.Errorf("%q. MetaClient.CreateUser() expected /user path but got %s", tt.name, req.URL.Path) - } - got, _ := ioutil.ReadAll(req.Body) - if string(got) != tt.want { - t.Errorf("%q. MetaClient.CreateUser() = %v, want %v", tt.name, string(got), tt.want) - } - } -} - -func TestMetaClient_ChangePassword(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - passwd string - } - tests := []struct { - name string - fields fields - args args - want string - wantErr bool - }{ - { - name: "Successful Change Password", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - nil, - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - passwd: "hunter2", - }, - want: `{"action":"change-password","user":{"name":"admin","password":"hunter2"}}`, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - if err := m.ChangePassword(tt.args.ctx, tt.args.name, tt.args.passwd); (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.ChangePassword() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - - if tt.wantErr { - continue - } - reqs := tt.fields.client.Requests - if len(reqs) != 1 { - t.Errorf("%q. MetaClient.ChangePassword() expected 1 but got %d", tt.name, len(reqs)) - continue - } - req := reqs[0] - if req.Method != "POST" { - t.Errorf("%q. MetaClient.ChangePassword() expected POST method", tt.name) - } - if req.URL.Path != "/user" { - t.Errorf("%q. MetaClient.ChangePassword() expected /user path but got %s", tt.name, req.URL.Path) - } - got, _ := ioutil.ReadAll(req.Body) - if string(got) != tt.want { - t.Errorf("%q. MetaClient.ChangePassword() = %v, want %v", tt.name, string(got), tt.want) - } - } -} - -func TestMetaClient_DeleteUser(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - } - tests := []struct { - name string - fields fields - args args - want string - wantErr bool - }{ - { - name: "Successful delete User", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - nil, - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - }, - want: `{"action":"delete","user":{"name":"admin"}}`, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - if err := m.DeleteUser(tt.args.ctx, tt.args.name); (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.DeleteUser() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - if tt.wantErr { - continue - } - reqs := tt.fields.client.Requests - if len(reqs) != 1 { - t.Errorf("%q. MetaClient.DeleteUser() expected 1 but got %d", tt.name, len(reqs)) - continue - } - req := reqs[0] - if req.Method != "POST" { - t.Errorf("%q. MetaClient.DeleteUser() expected POST method", tt.name) - } - if req.URL.Path != "/user" { - t.Errorf("%q. MetaClient.DeleteUser() expected /user path but got %s", tt.name, req.URL.Path) - } - got, _ := ioutil.ReadAll(req.Body) - if string(got) != tt.want { - t.Errorf("%q. MetaClient.DeleteUser() = %v, want %v", tt.name, string(got), tt.want) - } - } -} - -func TestMetaClient_SetUserPerms(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - perms Permissions - } - tests := []struct { - name string - fields fields - args args - wantRm string - wantAdd string - wantErr bool - }{ - { - name: "Remove all permissions for a user", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - }, - wantRm: `{"action":"remove-permissions","user":{"name":"admin","permissions":{"":["ViewAdmin","ViewChronograf"]}}}`, - }, - { - name: "Remove some permissions and add others", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"users":[{"name":"admin","hash":"1234","permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - perms: Permissions{ - "telegraf": []string{ - "ReadData", - }, - }, - }, - wantRm: `{"action":"remove-permissions","user":{"name":"admin","permissions":{"":["ViewAdmin","ViewChronograf"]}}}`, - wantAdd: `{"action":"add-permissions","user":{"name":"admin","permissions":{"telegraf":["ReadData"]}}}`, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - if err := m.SetUserPerms(tt.args.ctx, tt.args.name, tt.args.perms); (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.SetUserPerms() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - if tt.wantErr { - continue - } - reqs := tt.fields.client.Requests - if len(reqs) < 2 { - t.Errorf("%q. MetaClient.SetUserPerms() expected 2 but got %d", tt.name, len(reqs)) - continue - } - - usr := reqs[0] - if usr.Method != "GET" { - t.Errorf("%q. MetaClient.SetUserPerms() expected GET method", tt.name) - } - if usr.URL.Path != "/user" { - t.Errorf("%q. MetaClient.SetUserPerms() expected /user path but got %s", tt.name, usr.URL.Path) - } - - prm := reqs[1] - if prm.Method != "POST" { - t.Errorf("%q. MetaClient.SetUserPerms() expected GET method", tt.name) - } - if prm.URL.Path != "/user" { - t.Errorf("%q. MetaClient.SetUserPerms() expected /user path but got %s", tt.name, prm.URL.Path) - } - - got, _ := ioutil.ReadAll(prm.Body) - if string(got) != tt.wantRm { - t.Errorf("%q. MetaClient.SetUserPerms() = %v, want %v", tt.name, string(got), tt.wantRm) - } - if tt.wantAdd != "" { - prm := reqs[2] - if prm.Method != "POST" { - t.Errorf("%q. MetaClient.SetUserPerms() expected GET method", tt.name) - } - if prm.URL.Path != "/user" { - t.Errorf("%q. MetaClient.SetUserPerms() expected /user path but got %s", tt.name, prm.URL.Path) - } - - got, _ := ioutil.ReadAll(prm.Body) - if string(got) != tt.wantAdd { - t.Errorf("%q. MetaClient.SetUserPerms() = %v, want %v", tt.name, string(got), tt.wantAdd) - } - } - } -} - -func TestMetaClient_Roles(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name *string - } - tests := []struct { - name string - fields fields - args args - want *Roles - wantErr bool - }{ - { - name: "Successful Show role", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: nil, - }, - want: &Roles{ - Roles: []Role{ - { - Name: "admin", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty"}, - }, - }, - }, - }, - { - name: "Successful Show role single role", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: &[]string{"admin"}[0], - }, - want: &Roles{ - Roles: []Role{ - { - Name: "admin", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty"}, - }, - }, - }, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - got, err := m.Roles(tt.args.ctx, tt.args.name) - if (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.Roles() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. MetaClient.Roles() = %v, want %v", tt.name, got, tt.want) - } - } -} - -func TestMetaClient_Role(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - } - tests := []struct { - name string - fields fields - args args - want *Role - wantErr bool - }{ - { - name: "Successful Show role", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - }, - want: &Role{ - Name: "admin", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty"}, - }, - }, - { - name: "No such role", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusNotFound, - []byte(`{"error":"user not found"}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "unknown", - }, - wantErr: true, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - got, err := m.Role(tt.args.ctx, tt.args.name) - if (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.Role() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. MetaClient.Role() = %v, want %v", tt.name, got, tt.want) - } - } -} - -func TestMetaClient_UserRoles(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name *string - } - tests := []struct { - name string - fields fields - args args - want map[string]Roles - wantErr bool - }{ - { - name: "Successful Show all roles", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"roles":[{"name":"timetravelers","users":["marty","docbrown"],"permissions":{"":["ViewAdmin","ViewChronograf"]}},{"name":"mcfly","users":["marty","george"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: nil, - }, - want: map[string]Roles{ - "marty": Roles{ - Roles: []Role{ - { - Name: "timetravelers", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty", "docbrown"}, - }, - { - Name: "mcfly", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty", "george"}, - }, - }, - }, - "docbrown": Roles{ - Roles: []Role{ - { - Name: "timetravelers", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty", "docbrown"}, - }, - }, - }, - "george": Roles{ - Roles: []Role{ - { - Name: "mcfly", - Permissions: map[string][]string{ - "": []string{ - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty", "george"}, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - got, err := m.UserRoles(tt.args.ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.UserRoles() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. MetaClient.UserRoles() = %v, want %v", tt.name, got, tt.want) - } - } -} - -func TestMetaClient_CreateRole(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - } - tests := []struct { - name string - fields fields - args args - want string - wantErr bool - }{ - { - name: "Successful Create Role", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - nil, - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - }, - want: `{"action":"create","role":{"name":"admin"}}`, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - if err := m.CreateRole(tt.args.ctx, tt.args.name); (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.CreateRole() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - reqs := tt.fields.client.Requests - if len(reqs) != 1 { - t.Errorf("%q. MetaClient.CreateRole() expected 1 but got %d", tt.name, len(reqs)) - continue - } - req := reqs[0] - if req.Method != "POST" { - t.Errorf("%q. MetaClient.CreateRole() expected POST method", tt.name) - } - if req.URL.Path != "/role" { - t.Errorf("%q. MetaClient.CreateRole() expected /role path but got %s", tt.name, req.URL.Path) - } - got, _ := ioutil.ReadAll(req.Body) - if string(got) != tt.want { - t.Errorf("%q. MetaClient.CreateRole() = %v, want %v", tt.name, string(got), tt.want) - } - } -} - -func TestMetaClient_DeleteRole(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - } - tests := []struct { - name string - fields fields - args args - want string - wantErr bool - }{ - { - name: "Successful delete role", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - nil, - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - }, - want: `{"action":"delete","role":{"name":"admin"}}`, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - if err := m.DeleteRole(tt.args.ctx, tt.args.name); (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.DeleteRole() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - if tt.wantErr { - continue - } - reqs := tt.fields.client.Requests - if len(reqs) != 1 { - t.Errorf("%q. MetaClient.DeleteRole() expected 1 but got %d", tt.name, len(reqs)) - continue - } - req := reqs[0] - if req.Method != "POST" { - t.Errorf("%q. MetaClient.DeleDeleteRoleteUser() expected POST method", tt.name) - } - if req.URL.Path != "/role" { - t.Errorf("%q. MetaClient.DeleteRole() expected /role path but got %s", tt.name, req.URL.Path) - } - got, _ := ioutil.ReadAll(req.Body) - if string(got) != tt.want { - t.Errorf("%q. MetaClient.DeleteRole() = %v, want %v", tt.name, string(got), tt.want) - } - } -} - -func TestMetaClient_SetRolePerms(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - perms Permissions - } - tests := []struct { - name string - fields fields - args args - wantRm string - wantAdd string - wantErr bool - }{ - { - name: "Remove all roles from user", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - }, - wantRm: `{"action":"remove-permissions","role":{"name":"admin","permissions":{"":["ViewAdmin","ViewChronograf"]}}}`, - }, - { - name: "Remove some users and add permissions to other", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - perms: Permissions{ - "telegraf": []string{ - "ReadData", - }, - }, - }, - wantRm: `{"action":"remove-permissions","role":{"name":"admin","permissions":{"":["ViewAdmin","ViewChronograf"]}}}`, - wantAdd: `{"action":"add-permissions","role":{"name":"admin","permissions":{"telegraf":["ReadData"]}}}`, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - if err := m.SetRolePerms(tt.args.ctx, tt.args.name, tt.args.perms); (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.SetRolePerms() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - if tt.wantErr { - continue - } - reqs := tt.fields.client.Requests - if len(reqs) < 2 { - t.Errorf("%q. MetaClient.SetRolePerms() expected 2 but got %d", tt.name, len(reqs)) - continue - } - - usr := reqs[0] - if usr.Method != "GET" { - t.Errorf("%q. MetaClient.SetRolePerms() expected GET method", tt.name) - } - if usr.URL.Path != "/role" { - t.Errorf("%q. MetaClient.SetRolePerms() expected /user path but got %s", tt.name, usr.URL.Path) - } - - prm := reqs[1] - if prm.Method != "POST" { - t.Errorf("%q. MetaClient.SetRolePerms() expected GET method", tt.name) - } - if prm.URL.Path != "/role" { - t.Errorf("%q. MetaClient.SetRolePerms() expected /role path but got %s", tt.name, prm.URL.Path) - } - - got, _ := ioutil.ReadAll(prm.Body) - if string(got) != tt.wantRm { - t.Errorf("%q. MetaClient.SetRolePerms() removal = \n%v\n, want \n%v\n", tt.name, string(got), tt.wantRm) - } - if tt.wantAdd != "" { - prm := reqs[2] - if prm.Method != "POST" { - t.Errorf("%q. MetaClient.SetRolePerms() expected GET method", tt.name) - } - if prm.URL.Path != "/role" { - t.Errorf("%q. MetaClient.SetRolePerms() expected /role path but got %s", tt.name, prm.URL.Path) - } - - got, _ := ioutil.ReadAll(prm.Body) - if string(got) != tt.wantAdd { - t.Errorf("%q. MetaClient.SetRolePerms() addition = \n%v\n, want \n%v\n", tt.name, string(got), tt.wantAdd) - } - } - } -} - -func TestMetaClient_SetRoleUsers(t *testing.T) { - type fields struct { - URL *url.URL - client *MockClient - } - type args struct { - ctx context.Context - name string - users []string - } - tests := []struct { - name string - fields fields - args args - wants []string - wantErr bool - }{ - { - name: "Successful set users role (remove user from role)", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"roles":[{"name":"admin","users":["marty"],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - }, - wants: []string{`{"action":"remove-users","role":{"name":"admin","users":["marty"]}}`}, - }, - { - name: "Successful set single user role", - fields: fields{ - URL: &url.URL{ - Host: "twinpinesmall.net:8091", - Scheme: "https", - }, - client: NewMockClient( - http.StatusOK, - []byte(`{"roles":[{"name":"admin","users":[],"permissions":{"":["ViewAdmin","ViewChronograf"]}}]}`), - nil, - nil, - ), - }, - args: args{ - ctx: context.Background(), - name: "admin", - users: []string{"marty"}, - }, - wants: []string{ - `{"action":"add-users","role":{"name":"admin","users":["marty"]}}`, - }, - }, - } - for _, tt := range tests { - m := &MetaClient{ - URL: tt.fields.URL, - client: tt.fields.client, - } - if err := m.SetRoleUsers(tt.args.ctx, tt.args.name, tt.args.users); (err != nil) != tt.wantErr { - t.Errorf("%q. MetaClient.SetRoleUsers() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - - if tt.wantErr { - continue - } - reqs := tt.fields.client.Requests - if len(reqs) != len(tt.wants)+1 { - t.Errorf("%q. MetaClient.SetRoleUsers() expected %d but got %d", tt.name, len(tt.wants)+1, len(reqs)) - continue - } - - usr := reqs[0] - if usr.Method != "GET" { - t.Errorf("%q. MetaClient.SetRoleUsers() expected GET method", tt.name) - } - if usr.URL.Path != "/role" { - t.Errorf("%q. MetaClient.SetRoleUsers() expected /user path but got %s", tt.name, usr.URL.Path) - } - for i := range tt.wants { - prm := reqs[i+1] - if prm.Method != "POST" { - t.Errorf("%q. MetaClient.SetRoleUsers() expected GET method", tt.name) - } - if prm.URL.Path != "/role" { - t.Errorf("%q. MetaClient.SetRoleUsers() expected /role path but got %s", tt.name, prm.URL.Path) - } - - got, _ := ioutil.ReadAll(prm.Body) - if string(got) != tt.wants[i] { - t.Errorf("%q. MetaClient.SetRoleUsers() = %v, want %v", tt.name, string(got), tt.wants[i]) - } - } - } -} - -type MockClient struct { - Code int // HTTP Status code - Body []byte - HeaderMap http.Header - Err error - - Requests []*http.Request -} - -func NewMockClient(code int, body []byte, headers http.Header, err error) *MockClient { - return &MockClient{ - Code: code, - Body: body, - HeaderMap: headers, - Err: err, - Requests: make([]*http.Request, 0), - } -} - -func (c *MockClient) Do(URL *url.URL, path, method string, authorizer influx.Authorizer, params map[string]string, body io.Reader) (*http.Response, error) { - if c == nil { - return nil, fmt.Errorf("nil MockClient") - } - if URL == nil { - return nil, fmt.Errorf("nil url") - } - if c.Err != nil { - return nil, c.Err - } - - // Record the request in the mock client - p := url.Values{} - for k, v := range params { - p.Add(k, v) - } - - URL.Path = path - URL.RawQuery = p.Encode() - - req, err := http.NewRequest(method, URL.String(), body) - if err != nil { - return nil, err - } - c.Requests = append(c.Requests, req) - - return &http.Response{ - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - StatusCode: c.Code, - Status: http.StatusText(c.Code), - Header: c.HeaderMap, - Body: ioutil.NopCloser(bytes.NewReader(c.Body)), - }, nil -} - -func Test_AuthedCheckRedirect_Do(t *testing.T) { - var ts2URL string - ts1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - want := http.Header{ - "Referer": []string{ts2URL}, - "Accept-Encoding": []string{"gzip"}, - "Authorization": []string{"hunter2"}, - } - for k, v := range want { - if !reflect.DeepEqual(r.Header[k], v) { - t.Errorf("Request.Header = %#v; want %#v", r.Header[k], v) - } - } - if t.Failed() { - w.Header().Set("Result", "got errors") - } else { - w.Header().Set("Result", "ok") - } - })) - defer ts1.Close() - - ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, ts1.URL, http.StatusFound) - })) - defer ts2.Close() - ts2URL = ts2.URL - - tr := &http.Transport{} - defer tr.CloseIdleConnections() - d := &defaultClient{} - c := &http.Client{ - Transport: tr, - CheckRedirect: d.AuthedCheckRedirect, - } - - req, _ := http.NewRequest("GET", ts2.URL, nil) - req.Header.Add("Cookie", "foo=bar") - req.Header.Add("Authorization", "hunter2") - req.Header.Add("Howdy", "doody") - req.Header.Set("User-Agent", "Darth Vader, an extraterrestrial from the Planet Vulcan") - - res, err := c.Do(req) - if err != nil { - t.Fatal(err) - } - - defer res.Body.Close() - if res.StatusCode != 200 { - t.Fatal(res.Status) - } - - if got := res.Header.Get("Result"); got != "ok" { - t.Errorf("result = %q; want ok", got) - } -} - -func Test_defaultClient_Do(t *testing.T) { - type args struct { - path string - method string - authorizer influx.Authorizer - params map[string]string - body io.Reader - } - tests := []struct { - name string - args args - want string - wantErr bool - }{ - { - name: "test authorizer", - args: args{ - path: "/tictactoe", - method: "GET", - authorizer: &influx.BasicAuth{ - Username: "Steven Falken", - Password: "JOSHUA", - }, - }, - want: "Basic U3RldmVuIEZhbGtlbjpKT1NIVUE=", - }, - { - name: "test authorizer", - args: args{ - path: "/tictactoe", - method: "GET", - authorizer: &influx.BearerJWT{ - Username: "minifig", - SharedSecret: "legos", - Now: func() time.Time { return time.Time{} }, - }, - }, - want: "Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOi02MjEzNTU5Njc0MCwidXNlcm5hbWUiOiJtaW5pZmlnIn0.uwFGBQ3MykqEmk9Zx0sBdJGefcESVEXG_qt0C1J8b_aS62EAES-Q1FwtURsbITNvSnfzMxYFnkbSG0AA1pEzWw", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/tictactoe" { - t.Fatal("Expected request to '/query' but was", r.URL.Path) - } - got, ok := r.Header["Authorization"] - if !ok { - t.Fatal("No Authorization header") - } - if got[0] != tt.want { - t.Fatalf("Expected auth %s got %s", tt.want, got) - } - rw.Write([]byte(`{}`)) - })) - defer ts.Close() - - d := &defaultClient{} - u, _ := url.Parse(ts.URL) - _, err := d.Do(u, tt.args.path, tt.args.method, tt.args.authorizer, tt.args.params, tt.args.body) - if (err != nil) != tt.wantErr { - t.Errorf("defaultClient.Do() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} diff --git a/chronograf/enterprise/mocks_test.go b/chronograf/enterprise/mocks_test.go deleted file mode 100644 index 628044ccf0a..00000000000 --- a/chronograf/enterprise/mocks_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package enterprise_test - -import ( - "context" - "encoding/json" - "net/url" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/enterprise" -) - -type ControlClient struct { - Cluster *enterprise.Cluster - ShowClustersCalled bool -} - -func NewMockControlClient(addr string) *ControlClient { - _, err := url.Parse(addr) - if err != nil { - panic(err) - } - - return &ControlClient{ - Cluster: &enterprise.Cluster{ - DataNodes: []enterprise.DataNode{ - enterprise.DataNode{ - HTTPAddr: addr, - }, - }, - }, - } -} - -func (cc *ControlClient) ShowCluster(context.Context) (*enterprise.Cluster, error) { - cc.ShowClustersCalled = true - return cc.Cluster, nil -} - -func (cc *ControlClient) User(ctx context.Context, name string) (*enterprise.User, error) { - return nil, nil -} - -func (cc *ControlClient) CreateUser(ctx context.Context, name, passwd string) error { - return nil -} - -func (cc *ControlClient) DeleteUser(ctx context.Context, name string) error { - return nil -} - -func (cc *ControlClient) ChangePassword(ctx context.Context, name, passwd string) error { - return nil -} - -func (cc *ControlClient) Users(ctx context.Context, name *string) (*enterprise.Users, error) { - return nil, nil -} - -func (cc *ControlClient) SetUserPerms(ctx context.Context, name string, perms enterprise.Permissions) error { - return nil -} - -func (cc *ControlClient) CreateRole(ctx context.Context, name string) error { - return nil -} - -func (cc *ControlClient) Role(ctx context.Context, name string) (*enterprise.Role, error) { - return nil, nil -} - -func (ccm *ControlClient) UserRoles(ctx context.Context) (map[string]enterprise.Roles, error) { - return nil, nil -} - -func (ccm *ControlClient) Roles(ctx context.Context, name *string) (*enterprise.Roles, error) { - return nil, nil -} - -func (cc *ControlClient) DeleteRole(ctx context.Context, name string) error { - return nil -} - -func (cc *ControlClient) SetRolePerms(ctx context.Context, name string, perms enterprise.Permissions) error { - return nil -} - -func (cc *ControlClient) SetRoleUsers(ctx context.Context, name string, users []string) error { - return nil -} - -func (cc *ControlClient) AddRoleUsers(ctx context.Context, name string, users []string) error { - return nil -} - -func (cc *ControlClient) RemoveRoleUsers(ctx context.Context, name string, users []string) error { - return nil -} - -type TimeSeries struct { - URLs []string - Response Response - - QueryCtr int -} - -type Response struct{} - -func (r *Response) MarshalJSON() ([]byte, error) { - return json.Marshal(r) -} - -func (ts *TimeSeries) Query(ctx context.Context, q chronograf.Query) (chronograf.Response, error) { - ts.QueryCtr++ - return &Response{}, nil -} - -func (ts *TimeSeries) Connect(ctx context.Context, src *chronograf.Source) error { - return nil -} - -func (ts *TimeSeries) Write(ctx context.Context, points []chronograf.Point) error { - return nil -} - -func (ts *TimeSeries) Users(ctx context.Context) chronograf.UsersStore { - return nil -} - -func (ts *TimeSeries) Roles(ctx context.Context) (chronograf.RolesStore, error) { - return nil, nil -} - -func (ts *TimeSeries) Permissions(ctx context.Context) chronograf.Permissions { - return chronograf.Permissions{} -} - -func NewMockTimeSeries(urls ...string) *TimeSeries { - return &TimeSeries{ - URLs: urls, - Response: Response{}, - } -} diff --git a/chronograf/enterprise/roles.go b/chronograf/enterprise/roles.go deleted file mode 100644 index 628a091cce0..00000000000 --- a/chronograf/enterprise/roles.go +++ /dev/null @@ -1,113 +0,0 @@ -package enterprise - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// RolesStore uses a control client operate on Influx Enterprise roles. Roles are -// groups of permissions applied to groups of users -type RolesStore struct { - Ctrl - Logger chronograf.Logger -} - -// Add creates a new Role in Influx Enterprise -// This must be done in three smaller steps: creating, setting permissions, setting users. -func (c *RolesStore) Add(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) { - if err := c.Ctrl.CreateRole(ctx, u.Name); err != nil { - return nil, err - } - if err := c.Ctrl.SetRolePerms(ctx, u.Name, ToEnterprise(u.Permissions)); err != nil { - return nil, err - } - - users := make([]string, len(u.Users)) - for i, u := range u.Users { - users[i] = u.Name - } - if err := c.Ctrl.SetRoleUsers(ctx, u.Name, users); err != nil { - return nil, err - } - return u, nil -} - -// Delete the Role from Influx Enterprise -func (c *RolesStore) Delete(ctx context.Context, u *chronograf.Role) error { - return c.Ctrl.DeleteRole(ctx, u.Name) -} - -// Get retrieves a Role if name exists. -func (c *RolesStore) Get(ctx context.Context, name string) (*chronograf.Role, error) { - role, err := c.Ctrl.Role(ctx, name) - if err != nil { - return nil, err - } - - // Hydrate all the users to gather their permissions and their roles. - users := make([]chronograf.User, len(role.Users)) - for i, u := range role.Users { - user, err := c.Ctrl.User(ctx, u) - if err != nil { - return nil, err - } - users[i] = chronograf.User{ - Name: user.Name, - Permissions: ToChronograf(user.Permissions), - } - } - return &chronograf.Role{ - Name: role.Name, - Permissions: ToChronograf(role.Permissions), - Users: users, - }, nil -} - -// Update the Role's permissions and roles -func (c *RolesStore) Update(ctx context.Context, u *chronograf.Role) error { - if u.Permissions != nil { - perms := ToEnterprise(u.Permissions) - if err := c.Ctrl.SetRolePerms(ctx, u.Name, perms); err != nil { - return err - } - } - if u.Users != nil { - users := make([]string, len(u.Users)) - for i, u := range u.Users { - users[i] = u.Name - } - return c.Ctrl.SetRoleUsers(ctx, u.Name, users) - } - return nil -} - -// All is all Roles in influx -func (c *RolesStore) All(ctx context.Context) ([]chronograf.Role, error) { - all, err := c.Ctrl.Roles(ctx, nil) - if err != nil { - return nil, err - } - - return all.ToChronograf(), nil -} - -// ToChronograf converts enterprise roles to chronograf -func (r *Roles) ToChronograf() []chronograf.Role { - res := make([]chronograf.Role, len(r.Roles)) - for i, role := range r.Roles { - users := make([]chronograf.User, len(role.Users)) - for i, user := range role.Users { - users[i] = chronograf.User{ - Name: user, - } - } - - res[i] = chronograf.Role{ - Name: role.Name, - Permissions: ToChronograf(role.Permissions), - Users: users, - } - } - return res -} diff --git a/chronograf/enterprise/roles_test.go b/chronograf/enterprise/roles_test.go deleted file mode 100644 index d82fef0a7fa..00000000000 --- a/chronograf/enterprise/roles_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package enterprise - -import ( - "reflect" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestRoles_ToChronograf(t *testing.T) { - tests := []struct { - name string - roles []Role - want []chronograf.Role - }{ - { - name: "empty roles", - roles: []Role{}, - want: []chronograf.Role{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - r := &Roles{ - Roles: tt.roles, - } - if got := r.ToChronograf(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("Roles.ToChronograf() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/chronograf/enterprise/types.go b/chronograf/enterprise/types.go deleted file mode 100644 index d3c241ca283..00000000000 --- a/chronograf/enterprise/types.go +++ /dev/null @@ -1,71 +0,0 @@ -package enterprise - -// Cluster is a collection of data nodes and non-data nodes within a -// Plutonium cluster. -type Cluster struct { - DataNodes []DataNode `json:"data"` - MetaNodes []Node `json:"meta"` -} - -// DataNode represents a data node in an Influx Enterprise Cluster -type DataNode struct { - ID uint64 `json:"id"` // Meta store ID. - TCPAddr string `json:"tcpAddr"` // RPC addr, e.g., host:8088. - HTTPAddr string `json:"httpAddr"` // Client addr, e.g., host:8086. - HTTPScheme string `json:"httpScheme"` // "http" or "https" for HTTP addr. - Status string `json:"status,omitempty"` // The cluster status of the node. -} - -// Node represent any meta or data node in an Influx Enterprise cluster -type Node struct { - ID uint64 `json:"id"` - Addr string `json:"addr"` - HTTPScheme string `json:"httpScheme"` - TCPAddr string `json:"tcpAddr"` -} - -// Permissions maps resources to a set of permissions. -// Specifically, it maps a database to a set of permissions -type Permissions map[string][]string - -// User represents an enterprise user. -type User struct { - Name string `json:"name"` - Password string `json:"password,omitempty"` - Permissions Permissions `json:"permissions,omitempty"` -} - -// Users represents a set of enterprise users. -type Users struct { - Users []User `json:"users,omitempty"` -} - -// UserAction represents and action to be taken with a user. -type UserAction struct { - Action string `json:"action"` - User *User `json:"user"` -} - -// Role is a restricted set of permissions assigned to a set of users. -type Role struct { - Name string `json:"name"` - NewName string `json:"newName,omitempty"` - Permissions Permissions `json:"permissions,omitempty"` - Users []string `json:"users,omitempty"` -} - -// Roles is a set of roles -type Roles struct { - Roles []Role `json:"roles,omitempty"` -} - -// RoleAction represents an action to be taken with a role. -type RoleAction struct { - Action string `json:"action"` - Role *Role `json:"role"` -} - -// Error is JSON error message return by Influx Enterprise's meta API. -type Error struct { - Error string `json:"error"` -} diff --git a/chronograf/enterprise/users.go b/chronograf/enterprise/users.go deleted file mode 100644 index 4651b8cef69..00000000000 --- a/chronograf/enterprise/users.go +++ /dev/null @@ -1,197 +0,0 @@ -package enterprise - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// UserStore uses a control client operate on Influx Enterprise users -type UserStore struct { - Ctrl - Logger chronograf.Logger -} - -// Add creates a new User in Influx Enterprise -func (c *UserStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - if err := c.Ctrl.CreateUser(ctx, u.Name, u.Passwd); err != nil { - return nil, err - } - perms := ToEnterprise(u.Permissions) - - if err := c.Ctrl.SetUserPerms(ctx, u.Name, perms); err != nil { - return nil, err - } - for _, role := range u.Roles { - if err := c.Ctrl.AddRoleUsers(ctx, role.Name, []string{u.Name}); err != nil { - return nil, err - } - } - - return c.Get(ctx, chronograf.UserQuery{Name: &u.Name}) -} - -// Delete the User from Influx Enterprise -func (c *UserStore) Delete(ctx context.Context, u *chronograf.User) error { - return c.Ctrl.DeleteUser(ctx, u.Name) -} - -// Num of users in Influx -func (c *UserStore) Num(ctx context.Context) (int, error) { - all, err := c.All(ctx) - if err != nil { - return 0, err - } - - return len(all), nil -} - -// Get retrieves a user if name exists. -func (c *UserStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil { - return nil, fmt.Errorf("query must specify name") - } - u, err := c.Ctrl.User(ctx, *q.Name) - if err != nil { - return nil, err - } - - ur, err := c.Ctrl.UserRoles(ctx) - if err != nil { - return nil, err - } - - role := ur[*q.Name] - cr := role.ToChronograf() - // For now we are removing all users from a role being returned. - for i, r := range cr { - r.Users = []chronograf.User{} - cr[i] = r - } - return &chronograf.User{ - Name: u.Name, - Permissions: ToChronograf(u.Permissions), - Roles: cr, - }, nil -} - -// Update the user's permissions or roles -func (c *UserStore) Update(ctx context.Context, u *chronograf.User) error { - // Only allow one type of change at a time. If it is a password - // change then do it and return without any changes to permissions - if u.Passwd != "" { - return c.Ctrl.ChangePassword(ctx, u.Name, u.Passwd) - } - - if u.Roles != nil { - // Make a list of the roles we want this user to have: - want := make([]string, len(u.Roles)) - for i, r := range u.Roles { - want[i] = r.Name - } - - // Find the list of all roles this user is currently in - userRoles, err := c.UserRoles(ctx) - if err != nil { - return nil - } - // Make a list of the roles the user currently has - roles := userRoles[u.Name] - have := make([]string, len(roles.Roles)) - for i, r := range roles.Roles { - have[i] = r.Name - } - - // Calculate the roles the user will be removed from and the roles the user - // will be added to. - revoke, add := Difference(want, have) - - // First, add the user to the new roles - for _, role := range add { - if err := c.Ctrl.AddRoleUsers(ctx, role, []string{u.Name}); err != nil { - return err - } - } - - // ... and now remove the user from an extra roles - for _, role := range revoke { - if err := c.Ctrl.RemoveRoleUsers(ctx, role, []string{u.Name}); err != nil { - return err - } - } - } - - if u.Permissions != nil { - perms := ToEnterprise(u.Permissions) - return c.Ctrl.SetUserPerms(ctx, u.Name, perms) - } - return nil -} - -// All is all users in influx -func (c *UserStore) All(ctx context.Context) ([]chronograf.User, error) { - all, err := c.Ctrl.Users(ctx, nil) - if err != nil { - return nil, err - } - - ur, err := c.Ctrl.UserRoles(ctx) - if err != nil { - return nil, err - } - - res := make([]chronograf.User, len(all.Users)) - for i, user := range all.Users { - role := ur[user.Name] - cr := role.ToChronograf() - // For now we are removing all users from a role being returned. - for i, r := range cr { - r.Users = []chronograf.User{} - cr[i] = r - } - - res[i] = chronograf.User{ - Name: user.Name, - Permissions: ToChronograf(user.Permissions), - Roles: cr, - } - } - return res, nil -} - -// ToEnterprise converts chronograf permission shape to enterprise -func ToEnterprise(perms chronograf.Permissions) Permissions { - res := Permissions{} - for _, perm := range perms { - if perm.Scope == chronograf.AllScope { - // Enterprise uses empty string as the key for all databases - res[""] = perm.Allowed - } else { - res[perm.Name] = perm.Allowed - } - } - return res -} - -// ToChronograf converts enterprise permissions shape to chronograf shape -func ToChronograf(perms Permissions) chronograf.Permissions { - res := chronograf.Permissions{} - for db, perm := range perms { - // Enterprise uses empty string as the key for all databases - if db == "" { - res = append(res, chronograf.Permission{ - Scope: chronograf.AllScope, - Allowed: perm, - }) - } else { - res = append(res, chronograf.Permission{ - Scope: chronograf.DBScope, - Name: db, - Allowed: perm, - }) - - } - } - return res -} diff --git a/chronograf/enterprise/users_test.go b/chronograf/enterprise/users_test.go deleted file mode 100644 index 0b1d0975d2e..00000000000 --- a/chronograf/enterprise/users_test.go +++ /dev/null @@ -1,866 +0,0 @@ -package enterprise_test - -import ( - "context" - "fmt" - "reflect" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/enterprise" -) - -func TestClient_Add(t *testing.T) { - type fields struct { - Ctrl *mockCtrl - Logger chronograf.Logger - } - type args struct { - ctx context.Context - u *chronograf.User - } - tests := []struct { - name string - fields fields - args args - want *chronograf.User - wantErr bool - }{ - { - name: "Successful Create User", - fields: fields{ - Ctrl: &mockCtrl{ - createUser: func(ctx context.Context, name, passwd string) error { - return nil - }, - setUserPerms: func(ctx context.Context, name string, perms enterprise.Permissions) error { - return nil - }, - user: func(ctx context.Context, name string) (*enterprise.User, error) { - return &enterprise.User{ - Name: "marty", - Password: "johnny be good", - Permissions: map[string][]string{ - "": { - "ViewChronograf", - "ReadData", - "WriteData", - }, - }, - }, nil - }, - userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) { - return map[string]enterprise.Roles{}, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Passwd: "johnny be good", - }, - }, - want: &chronograf.User{ - Name: "marty", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"}, - }, - }, - Roles: []chronograf.Role{}, - }, - }, - { - name: "Successful Create User with roles", - fields: fields{ - Ctrl: &mockCtrl{ - createUser: func(ctx context.Context, name, passwd string) error { - return nil - }, - setUserPerms: func(ctx context.Context, name string, perms enterprise.Permissions) error { - return nil - }, - user: func(ctx context.Context, name string) (*enterprise.User, error) { - return &enterprise.User{ - Name: "marty", - Password: "johnny be good", - Permissions: map[string][]string{ - "": { - "ViewChronograf", - "ReadData", - "WriteData", - }, - }, - }, nil - }, - userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) { - return map[string]enterprise.Roles{ - "marty": enterprise.Roles{ - Roles: []enterprise.Role{ - { - Name: "admin", - }, - }, - }, - }, nil - }, - addRoleUsers: func(ctx context.Context, name string, users []string) error { - return nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Passwd: "johnny be good", - Roles: []chronograf.Role{ - { - Name: "admin", - }, - }, - }, - }, - want: &chronograf.User{ - Name: "marty", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"}, - }, - }, - Roles: []chronograf.Role{ - { - Name: "admin", - Users: []chronograf.User{}, - Permissions: chronograf.Permissions{}, - }, - }, - }, - }, - { - name: "Failure to Create User", - fields: fields{ - Ctrl: &mockCtrl{ - createUser: func(ctx context.Context, name, passwd string) error { - return fmt.Errorf("1.21 Gigawatts! Tom, how could I have been so careless?") - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Passwd: "johnny be good", - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - c := &enterprise.UserStore{ - Ctrl: tt.fields.Ctrl, - Logger: tt.fields.Logger, - } - got, err := c.Add(tt.args.ctx, tt.args.u) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Client.Add() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. Client.Add() = \n%#v\n, want \n%#v\n", tt.name, got, tt.want) - } - } -} - -func TestClient_Delete(t *testing.T) { - type fields struct { - Ctrl *mockCtrl - Logger chronograf.Logger - } - type args struct { - ctx context.Context - u *chronograf.User - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "Successful Delete User", - fields: fields{ - Ctrl: &mockCtrl{ - deleteUser: func(ctx context.Context, name string) error { - return nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Passwd: "johnny be good", - }, - }, - }, - { - name: "Failure to Delete User", - fields: fields{ - Ctrl: &mockCtrl{ - deleteUser: func(ctx context.Context, name string) error { - return fmt.Errorf("1.21 Gigawatts! Tom, how could I have been so careless?") - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Passwd: "johnny be good", - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - c := &enterprise.UserStore{ - Ctrl: tt.fields.Ctrl, - Logger: tt.fields.Logger, - } - if err := c.Delete(tt.args.ctx, tt.args.u); (err != nil) != tt.wantErr { - t.Errorf("%q. Client.Delete() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - } -} - -func TestClient_Get(t *testing.T) { - type fields struct { - Ctrl *mockCtrl - Logger chronograf.Logger - } - type args struct { - ctx context.Context - name string - } - tests := []struct { - name string - fields fields - args args - want *chronograf.User - wantErr bool - }{ - { - name: "Successful Get User", - fields: fields{ - Ctrl: &mockCtrl{ - user: func(ctx context.Context, name string) (*enterprise.User, error) { - return &enterprise.User{ - Name: "marty", - Password: "johnny be good", - Permissions: map[string][]string{ - "": { - "ViewChronograf", - "ReadData", - "WriteData", - }, - }, - }, nil - }, - userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) { - return map[string]enterprise.Roles{}, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - name: "marty", - }, - want: &chronograf.User{ - Name: "marty", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"}, - }, - }, - Roles: []chronograf.Role{}, - }, - }, - { - name: "Successful Get User with roles", - fields: fields{ - Ctrl: &mockCtrl{ - user: func(ctx context.Context, name string) (*enterprise.User, error) { - return &enterprise.User{ - Name: "marty", - Password: "johnny be good", - Permissions: map[string][]string{ - "": { - "ViewChronograf", - "ReadData", - "WriteData", - }, - }, - }, nil - }, - userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) { - return map[string]enterprise.Roles{ - "marty": enterprise.Roles{ - Roles: []enterprise.Role{ - { - Name: "timetravels", - Permissions: map[string][]string{ - "": { - "ViewChronograf", - "ReadData", - "WriteData", - }, - }, - Users: []string{"marty", "docbrown"}, - }, - }, - }, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - name: "marty", - }, - want: &chronograf.User{ - Name: "marty", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"}, - }, - }, - Roles: []chronograf.Role{ - { - Name: "timetravels", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"}, - }, - }, - Users: []chronograf.User{}, - }, - }, - }, - }, - { - name: "Failure to get User", - fields: fields{ - Ctrl: &mockCtrl{ - user: func(ctx context.Context, name string) (*enterprise.User, error) { - return nil, fmt.Errorf("1.21 Gigawatts! Tom, how could I have been so careless?") - }, - }, - }, - args: args{ - ctx: context.Background(), - name: "marty", - }, - wantErr: true, - }, - } - for _, tt := range tests { - c := &enterprise.UserStore{ - Ctrl: tt.fields.Ctrl, - Logger: tt.fields.Logger, - } - got, err := c.Get(tt.args.ctx, chronograf.UserQuery{Name: &tt.args.name}) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Client.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. Client.Get() = %v, want %v", tt.name, got, tt.want) - } - } -} - -func TestClient_Update(t *testing.T) { - type fields struct { - Ctrl *mockCtrl - Logger chronograf.Logger - } - type args struct { - ctx context.Context - u *chronograf.User - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "Successful Change Password", - fields: fields{ - Ctrl: &mockCtrl{ - changePassword: func(ctx context.Context, name, passwd string) error { - return nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Passwd: "johnny be good", - }, - }, - }, - { - name: "Failure to Change Password", - fields: fields{ - Ctrl: &mockCtrl{ - changePassword: func(ctx context.Context, name, passwd string) error { - return fmt.Errorf("ronald Reagan, the actor?! Ha Then who’s Vice President Jerry Lewis? I suppose Jane Wyman is First Lady") - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Passwd: "johnny be good", - }, - }, - wantErr: true, - }, - { - name: "Success setting permissions User", - fields: fields{ - Ctrl: &mockCtrl{ - setUserPerms: func(ctx context.Context, name string, perms enterprise.Permissions) error { - return nil - }, - userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) { - return map[string]enterprise.Roles{}, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "KapacitorAPI"}, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "Success setting permissions and roles for user", - fields: fields{ - Ctrl: &mockCtrl{ - setUserPerms: func(ctx context.Context, name string, perms enterprise.Permissions) error { - return nil - }, - addRoleUsers: func(ctx context.Context, name string, users []string) error { - return nil - }, - userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) { - return map[string]enterprise.Roles{}, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "KapacitorAPI"}, - }, - }, - Roles: []chronograf.Role{ - { - Name: "adminrole", - }, - }, - }, - }, - wantErr: false, - }, - { - name: "Failure setting permissions User", - fields: fields{ - Ctrl: &mockCtrl{ - setUserPerms: func(ctx context.Context, name string, perms enterprise.Permissions) error { - return fmt.Errorf("they found me, I don't know how, but they found me.") - }, - userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) { - return map[string]enterprise.Roles{}, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "marty", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "KapacitorAPI"}, - }, - }, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - c := &enterprise.UserStore{ - Ctrl: tt.fields.Ctrl, - Logger: tt.fields.Logger, - } - if err := c.Update(tt.args.ctx, tt.args.u); (err != nil) != tt.wantErr { - t.Errorf("%q. Client.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - } -} - -func TestClient_Num(t *testing.T) { - type fields struct { - Ctrl *mockCtrl - Logger chronograf.Logger - } - type args struct { - ctx context.Context - } - tests := []struct { - name string - fields fields - args args - want []chronograf.User - wantErr bool - }{ - { - name: "Successful Get User", - fields: fields{ - Ctrl: &mockCtrl{ - users: func(ctx context.Context, name *string) (*enterprise.Users, error) { - return &enterprise.Users{ - Users: []enterprise.User{ - { - Name: "marty", - Password: "johnny be good", - Permissions: map[string][]string{ - "": { - "ViewChronograf", - "ReadData", - "WriteData", - }, - }, - }, - }, - }, nil - }, - userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) { - return map[string]enterprise.Roles{}, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - }, - want: []chronograf.User{ - { - Name: "marty", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"}, - }, - }, - Roles: []chronograf.Role{}, - }, - }, - }, - { - name: "Failure to get User", - fields: fields{ - Ctrl: &mockCtrl{ - users: func(ctx context.Context, name *string) (*enterprise.Users, error) { - return nil, fmt.Errorf("1.21 Gigawatts! Tom, how could I have been so careless?") - }, - }, - }, - args: args{ - ctx: context.Background(), - }, - wantErr: true, - }, - } - for _, tt := range tests { - c := &enterprise.UserStore{ - Ctrl: tt.fields.Ctrl, - Logger: tt.fields.Logger, - } - got, err := c.Num(tt.args.ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Client.Num() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != len(tt.want) { - t.Errorf("%q. Client.Num() = %v, want %v", tt.name, got, len(tt.want)) - } - } -} - -func TestClient_All(t *testing.T) { - type fields struct { - Ctrl *mockCtrl - Logger chronograf.Logger - } - type args struct { - ctx context.Context - } - tests := []struct { - name string - fields fields - args args - want []chronograf.User - wantErr bool - }{ - { - name: "Successful Get User", - fields: fields{ - Ctrl: &mockCtrl{ - users: func(ctx context.Context, name *string) (*enterprise.Users, error) { - return &enterprise.Users{ - Users: []enterprise.User{ - { - Name: "marty", - Password: "johnny be good", - Permissions: map[string][]string{ - "": { - "ViewChronograf", - "ReadData", - "WriteData", - }, - }, - }, - }, - }, nil - }, - userRoles: func(ctx context.Context) (map[string]enterprise.Roles, error) { - return map[string]enterprise.Roles{}, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - }, - want: []chronograf.User{ - { - Name: "marty", - Permissions: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "ReadData", "WriteData"}, - }, - }, - Roles: []chronograf.Role{}, - }, - }, - }, - { - name: "Failure to get User", - fields: fields{ - Ctrl: &mockCtrl{ - users: func(ctx context.Context, name *string) (*enterprise.Users, error) { - return nil, fmt.Errorf("1.21 Gigawatts! Tom, how could I have been so careless?") - }, - }, - }, - args: args{ - ctx: context.Background(), - }, - wantErr: true, - }, - } - for _, tt := range tests { - c := &enterprise.UserStore{ - Ctrl: tt.fields.Ctrl, - Logger: tt.fields.Logger, - } - got, err := c.All(tt.args.ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. Client.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. Client.All() = %v, want %v", tt.name, got, tt.want) - } - } -} - -func Test_ToEnterprise(t *testing.T) { - tests := []struct { - name string - perms chronograf.Permissions - want enterprise.Permissions - }{ - { - name: "All Scopes", - want: enterprise.Permissions{"": []string{"ViewChronograf", "KapacitorAPI"}}, - perms: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "KapacitorAPI"}, - }, - }, - }, - { - name: "DB Scope", - want: enterprise.Permissions{"telegraf": []string{"ReadData", "WriteData"}}, - perms: chronograf.Permissions{ - { - Scope: chronograf.DBScope, - Name: "telegraf", - Allowed: chronograf.Allowances{"ReadData", "WriteData"}, - }, - }, - }, - } - for _, tt := range tests { - if got := enterprise.ToEnterprise(tt.perms); !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. ToEnterprise() = %v, want %v", tt.name, got, tt.want) - } - } -} - -func Test_ToChronograf(t *testing.T) { - tests := []struct { - name string - perms enterprise.Permissions - want chronograf.Permissions - }{ - { - name: "All Scopes", - perms: enterprise.Permissions{"": []string{"ViewChronograf", "KapacitorAPI"}}, - want: chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"ViewChronograf", "KapacitorAPI"}, - }, - }, - }, - { - name: "DB Scope", - perms: enterprise.Permissions{"telegraf": []string{"ReadData", "WriteData"}}, - want: chronograf.Permissions{ - { - Scope: chronograf.DBScope, - Name: "telegraf", - Allowed: chronograf.Allowances{"ReadData", "WriteData"}, - }, - }, - }, - } - for _, tt := range tests { - if got := enterprise.ToChronograf(tt.perms); !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. toChronograf() = %v, want %v", tt.name, got, tt.want) - } - } -} - -type mockCtrl struct { - showCluster func(ctx context.Context) (*enterprise.Cluster, error) - user func(ctx context.Context, name string) (*enterprise.User, error) - createUser func(ctx context.Context, name, passwd string) error - deleteUser func(ctx context.Context, name string) error - changePassword func(ctx context.Context, name, passwd string) error - users func(ctx context.Context, name *string) (*enterprise.Users, error) - setUserPerms func(ctx context.Context, name string, perms enterprise.Permissions) error - - userRoles func(ctx context.Context) (map[string]enterprise.Roles, error) - - roles func(ctx context.Context, name *string) (*enterprise.Roles, error) - role func(ctx context.Context, name string) (*enterprise.Role, error) - createRole func(ctx context.Context, name string) error - deleteRole func(ctx context.Context, name string) error - setRolePerms func(ctx context.Context, name string, perms enterprise.Permissions) error - setRoleUsers func(ctx context.Context, name string, users []string) error - addRoleUsers func(ctx context.Context, name string, users []string) error - removeRoleUsers func(ctx context.Context, name string, users []string) error -} - -func (m *mockCtrl) ShowCluster(ctx context.Context) (*enterprise.Cluster, error) { - return m.showCluster(ctx) -} - -func (m *mockCtrl) User(ctx context.Context, name string) (*enterprise.User, error) { - return m.user(ctx, name) -} - -func (m *mockCtrl) CreateUser(ctx context.Context, name, passwd string) error { - return m.createUser(ctx, name, passwd) -} - -func (m *mockCtrl) DeleteUser(ctx context.Context, name string) error { - return m.deleteUser(ctx, name) -} - -func (m *mockCtrl) ChangePassword(ctx context.Context, name, passwd string) error { - return m.changePassword(ctx, name, passwd) -} - -func (m *mockCtrl) Users(ctx context.Context, name *string) (*enterprise.Users, error) { - return m.users(ctx, name) -} - -func (m *mockCtrl) SetUserPerms(ctx context.Context, name string, perms enterprise.Permissions) error { - return m.setUserPerms(ctx, name, perms) -} - -func (m *mockCtrl) UserRoles(ctx context.Context) (map[string]enterprise.Roles, error) { - return m.userRoles(ctx) -} - -func (m *mockCtrl) Roles(ctx context.Context, name *string) (*enterprise.Roles, error) { - return m.roles(ctx, name) -} - -func (m *mockCtrl) Role(ctx context.Context, name string) (*enterprise.Role, error) { - return m.role(ctx, name) -} - -func (m *mockCtrl) CreateRole(ctx context.Context, name string) error { - return m.createRole(ctx, name) -} - -func (m *mockCtrl) DeleteRole(ctx context.Context, name string) error { - return m.deleteRole(ctx, name) -} - -func (m *mockCtrl) SetRolePerms(ctx context.Context, name string, perms enterprise.Permissions) error { - return m.setRolePerms(ctx, name, perms) -} - -func (m *mockCtrl) SetRoleUsers(ctx context.Context, name string, users []string) error { - return m.setRoleUsers(ctx, name, users) -} - -func (m *mockCtrl) AddRoleUsers(ctx context.Context, name string, users []string) error { - return m.addRoleUsers(ctx, name, users) -} - -func (m *mockCtrl) RemoveRoleUsers(ctx context.Context, name string, users []string) error { - return m.removeRoleUsers(ctx, name, users) -} diff --git a/chronograf/etc/Dockerfile_build b/chronograf/etc/Dockerfile_build deleted file mode 100644 index 2e91918bb11..00000000000 --- a/chronograf/etc/Dockerfile_build +++ /dev/null @@ -1,44 +0,0 @@ -FROM ubuntu:trusty - -RUN apt update && DEBIAN_FRONTEND=noninteractive apt install -y \ - apt-transport-https \ - python-dev \ - wget \ - curl \ - git \ - mercurial \ - make \ - ruby \ - ruby-dev \ - rpm \ - zip \ - python-pip \ - autoconf \ - libtool - -RUN pip install boto requests python-jose --upgrade -RUN gem install fpm - -# Install node -ENV NODE_VERSION v8.10.0 -RUN wget -q https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz; \ - tar -xvf node-${NODE_VERSION}-linux-x64.tar.gz -C / --strip-components=1; \ - rm -f node-${NODE_VERSION}-linux-x64.tar.gz - -# Install go -ENV GOPATH /root/go -ENV GO_VERSION 1.10 -ENV GO_ARCH amd64 -RUN wget https://storage.googleapis.com/golang/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz; \ - tar -C /usr/local/ -xf /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz ; \ - rm /go${GO_VERSION}.linux-${GO_ARCH}.tar.gz -ENV PATH /usr/local/go/bin:$PATH - -ENV PROJECT_DIR $GOPATH/src/github.com/influxdata/influxdb/chronograf -ENV PATH $GOPATH/bin:$PATH -RUN mkdir -p $PROJECT_DIR -WORKDIR $PROJECT_DIR - -VOLUME $PROJECT_DIR - -ENTRYPOINT [ "/root/go/src/github.com/influxdata/influxdb/chronograf/etc/build.py" ] diff --git a/chronograf/etc/README.md b/chronograf/etc/README.md deleted file mode 100644 index d94fd49056f..00000000000 --- a/chronograf/etc/README.md +++ /dev/null @@ -1,15 +0,0 @@ -## Builds - -Builds are run from a docker build image that is configured with the node and go we support. -Our circle.yml uses this docker container to build, test and create release packages. - -### Updating new node/go versions -After updating the Dockerfile_build run - -`docker build -t quay.io/influxdb/builder:chronograf-$(date "+%Y%m%d") -f Dockerfile_build .` - -and push to quay with: -`docker push quay.io/influxdb/builder:chronograf-$(date "+%Y%m%d")` - -### Update circle -Update DOCKER_TAG in circle.yml to the new container. diff --git a/chronograf/etc/build.py b/chronograf/etc/build.py deleted file mode 100755 index 833500dbe05..00000000000 --- a/chronograf/etc/build.py +++ /dev/null @@ -1,1054 +0,0 @@ -#!/usr/bin/python -u - -import sys -import os -import subprocess -from datetime import datetime -import shutil -import tempfile -import hashlib -import re -import logging -import argparse -import json -import fs - -################ -#### Chronograf Variables -################ - -# Packaging variables -PACKAGE_NAME = "chronograf" -INSTALL_ROOT_DIR = "/usr/bin" -LOG_DIR = "/var/log/chronograf" -DATA_DIR = "/var/lib/chronograf" -SCRIPT_DIR = "/usr/lib/chronograf/scripts" -LOGROTATE_DIR = "/etc/logrotate.d" -CANNED_DIR = "/usr/share/chronograf/canned" -RESOURCES_DIR = "/usr/share/chronograf/resources" - -INIT_SCRIPT = "etc/scripts/init.sh" -SYSTEMD_SCRIPT = "etc/scripts/chronograf.service" -POSTINST_SCRIPT = "etc/scripts/post-install.sh" -POSTUNINST_SCRIPT = "etc/scripts/post-uninstall.sh" -LOGROTATE_SCRIPT = "etc/scripts/logrotate" -CANNED_SCRIPTS = "canned/*json" - -# Default AWS S3 bucket for uploads -DEFAULT_BUCKET = "dl.influxdata.com/chronograf/artifacts" - -CONFIGURATION_FILES = [ - LOGROTATE_DIR + '/chronograf', -] - -PACKAGE_LICENSE = "AGPLv3" -PACKAGE_URL = "https://github.com/influxdata/influxdb/chronograf" -MAINTAINER = "contact@influxdb.com" -VENDOR = "InfluxData" -DESCRIPTION = "Open source monitoring and visualization UI for the entire TICK stack." - -prereqs = [ 'git', 'go', 'yarn' ] -go_vet_command = "go tool vet ./" -optional_prereqs = [ 'fpm', 'rpmbuild', 'gpg' ] - -fpm_common_args = "-f -s dir --log error \ ---vendor {} \ ---url {} \ ---after-install {} \ ---after-remove {} \ ---license {} \ ---maintainer {} \ ---directories {} \ ---directories {} \ ---description \"{}\"".format( - VENDOR, - PACKAGE_URL, - POSTINST_SCRIPT, - POSTUNINST_SCRIPT, - PACKAGE_LICENSE, - MAINTAINER, - LOG_DIR, - DATA_DIR, - DESCRIPTION) - -for f in CONFIGURATION_FILES: - fpm_common_args += " --config-files {}".format(f) - -targets = { - 'chronograf' : './cmd/chronograf', - 'chronoctl' : './cmd/chronoctl', -} - -supported_builds = { - 'darwin': [ "amd64" ], - 'windows': [ "amd64" ], - 'linux': [ "amd64", "i386", "armhf", "arm64", "armel", "static_i386", "static_amd64" ] -} - -supported_packages = { - "darwin": [ "tar" ], - "linux": [ "deb", "rpm", "tar" ], - "windows": [ "zip" ], - "freebsd": [ "tar" ] -} - -################ -#### Chronograf Functions -################ - -def print_banner(): - logging.info(""" - ___ _ __ - / __| |_ _ _ ___ _ _ ___ __ _ _ _ __ _ / _| - | (__| ' \| '_/ _ \ ' \/ _ \/ _` | '_/ _` | _| - \___|_||_|_| \___/_||_\___/\__, |_| \__,_|_| - |___/ - Build Script -""") - -def create_package_fs(build_root): - """Create a filesystem structure to mimic the package filesystem. - """ - logging.debug("Creating package filesystem at location: {}".format(build_root)) - # Using [1:] for the path names due to them being absolute - # (will overwrite previous paths, per 'os.path.join' documentation) - dirs = [ - INSTALL_ROOT_DIR[1:], - LOG_DIR[1:], - DATA_DIR[1:], - SCRIPT_DIR[1:], - LOGROTATE_DIR[1:], - CANNED_DIR[1:], - RESOURCES_DIR[1:] - ] - for d in dirs: - os.makedirs(os.path.join(build_root, d)) - os.chmod(os.path.join(build_root, d), 0o755) - -def package_scripts(build_root, config_only=False, windows=False): - """Copy the necessary scripts to the package filesystem. - """ - if config_only: - pass - else: - logging.debug("Copying scripts to build directory.") - files = [ - (INIT_SCRIPT, SCRIPT_DIR, "init.sh"), - (SYSTEMD_SCRIPT, SCRIPT_DIR, "chronograf.service"), - (LOGROTATE_SCRIPT, LOGROTATE_DIR, "chronograf") - ] - for script, dir, name in files: - dest = os.path.join(build_root, dir[1:], name) - logging.debug("Moving {} to {}".format(script, dest)) - shutil.copyfile(script, dest) - os.chmod(dest, 0o644) - run("cp {} {} && chmod 644 {}".format(CANNED_SCRIPTS, - os.path.join(build_root, CANNED_DIR[1:]), - os.path.join(build_root, CANNED_DIR[1:], "*json")), - shell=True, print_output=True) - -def run_generate(): - """Generate static assets. - """ - start_time = datetime.utcnow() - logging.info("Generating static assets...") - run("make assets", shell=True, print_output=True) - end_time = datetime.utcnow() - logging.info("Time taken: {}s".format((end_time - start_time).total_seconds())) - return True - -def make_clean(): - """Generate static assets. - """ - start_time = datetime.utcnow() - run("make clean", shell=True, print_output=True) - end_time = datetime.utcnow() - logging.info("Time taken: {}s".format((end_time - start_time).total_seconds())) - return True - - -def go_get(branch, update=False, no_uncommitted=False): - """Retrieve build dependencies or restore pinned dependencies. - """ - start_time = datetime.utcnow() - if local_changes() and no_uncommitted: - logging.error("There are uncommitted changes in the current directory.") - return False - run("make dep", shell=True, print_output=True) - end_time = datetime.utcnow() - logging.info("Time taken: {}s".format((end_time - start_time).total_seconds())) - return True - -def run_tests(race, parallel, timeout, no_vet): - """Run the Go and NPM test suite on binary output. - """ - start_time = datetime.utcnow() - logging.info("Running tests...") - run("make test", shell=True, print_output=True) - end_time = datetime.utcnow() - logging.info("Time taken: {}s".format((end_time - start_time).total_seconds())) - return True - -################ -#### All Chronograf-specific content above this line -################ - -def run(command, allow_failure=False, shell=False, print_output=False): - """Run shell command (convenience wrapper around subprocess). - """ - out = None - logging.debug("{}".format(command)) - try: - cmd = command - if not shell: - cmd = command.split() - - stdout = subprocess.PIPE - stderr = subprocess.STDOUT - if print_output: - stdout = None - - p = subprocess.Popen(cmd, shell=shell, stdout=stdout, stderr=stderr) - out, _ = p.communicate() - if out is not None: - out = out.decode('utf-8').strip() - if p.returncode != 0: - if allow_failure: - logging.warn(u"Command '{}' failed with error: {}".format(command, out)) - return None - else: - logging.error(u"Command '{}' failed with error: {}".format(command, out)) - sys.exit(1) - except OSError as e: - if allow_failure: - logging.warn("Command '{}' failed with error: {}".format(command, e)) - return out - else: - logging.error("Command '{}' failed with error: {}".format(command, e)) - sys.exit(1) - else: - return out - -def create_temp_dir(prefix = None): - """ Create temporary directory with optional prefix. - """ - if prefix is None: - return tempfile.mkdtemp(prefix="{}-build.".format(PACKAGE_NAME)) - else: - return tempfile.mkdtemp(prefix=prefix) - -def increment_minor_version(version): - """Return the version with the minor version incremented and patch - version set to zero. - """ - ver_list = version.split('.') - if len(ver_list) != 3: - logging.warn("Could not determine how to increment version '{}', will just use provided version.".format(version)) - return version - ver_list[1] = str(int(ver_list[1]) + 1) - ver_list[2] = str(0) - inc_version = '.'.join(ver_list) - logging.debug("Incremented version from '{}' to '{}'.".format(version, inc_version)) - return inc_version - -def get_current_version_tag(): - """Retrieve the raw git version tag. - """ - version = run("git describe --always --tags --abbrev=0") - return version - -def get_current_version(): - """Parse version information from git tag output. - """ - version_tag = get_current_version_tag() - # Remove leading 'v' - if version_tag[0] == 'v': - version_tag = version_tag[1:] - # Replace any '-'/'_' with '~' - if '-' in version_tag: - version_tag = version_tag.replace("-","~") - if '_' in version_tag: - version_tag = version_tag.replace("_","~") - return version_tag - -def get_current_commit(short=False): - """Retrieve the current git commit. - """ - command = None - if short: - command = "git log --pretty=format:'%h' -n 1" - else: - command = "git rev-parse HEAD" - out = run(command) - return out.strip('\'\n\r ') - -def get_current_branch(): - """Retrieve the current git branch. - """ - command = "git rev-parse --abbrev-ref HEAD" - out = run(command) - return out.strip() - -def local_changes(): - """Return True if there are local un-committed changes. - """ - output = run("git diff-files --ignore-submodules --").strip() - if len(output) > 0: - return True - return False - -def get_system_arch(): - """Retrieve current system architecture. - """ - arch = os.uname()[4] - if arch == "x86_64": - arch = "amd64" - elif arch == "386": - arch = "i386" - elif 'arm' in arch: - # Prevent uname from reporting full ARM arch (eg 'armv7l') - arch = "arm" - return arch - -def get_system_platform(): - """Retrieve current system platform. - """ - if sys.platform.startswith("linux"): - return "linux" - else: - return sys.platform - -def get_go_version(): - """Retrieve version information for Go. - """ - out = run("go version") - matches = re.search('go version go(\S+)', out) - if matches is not None: - return matches.groups()[0].strip() - return None - -def check_path_for(b): - """Check the the user's path for the provided binary. - """ - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - for path in os.environ["PATH"].split(os.pathsep): - path = path.strip('"') - full_path = os.path.join(path, b) - if os.path.isfile(full_path) and os.access(full_path, os.X_OK): - return full_path - -def check_environ(build_dir = None): - """Check environment for common Go variables. - """ - logging.info("Checking environment...") - for v in [ "GOPATH", "GOBIN", "GOROOT" ]: - logging.debug("Using '{}' for {}".format(os.environ.get(v), v)) - - cwd = os.getcwd() - if build_dir is None and os.environ.get("GOPATH") and os.environ.get("GOPATH") not in cwd: - logging.warn("Your current directory is not under your GOPATH. This may lead to build failures.") - return True - -def check_prereqs(): - """Check user path for required dependencies. - """ - logging.info("Checking for dependencies...") - for req in prereqs: - if not check_path_for(req): - logging.error("Could not find dependency: {}".format(req)) - return False - return True - -def upload_packages(packages, bucket_name=None, overwrite=False): - """Upload provided package output to AWS S3. - """ - logging.debug("Uploading files to bucket '{}': {}".format(bucket_name, packages)) - try: - import boto - from boto.s3.key import Key - from boto.s3.connection import OrdinaryCallingFormat - logging.getLogger("boto").setLevel(logging.WARNING) - except ImportError: - logging.warn("Cannot upload packages without 'boto' Python library!") - return False - logging.info("Connecting to AWS S3...") - # Up the number of attempts to 10 from default of 1 - boto.config.add_section("Boto") - boto.config.set("Boto", "metadata_service_num_attempts", "10") - c = boto.connect_s3(calling_format=OrdinaryCallingFormat()) - if bucket_name is None: - bucket_name = DEFAULT_BUCKET - bucket = c.get_bucket(bucket_name.split('/')[0]) - for p in packages: - if '/' in bucket_name: - # Allow for nested paths within the bucket name (ex: - # bucket/folder). Assuming forward-slashes as path - # delimiter. - name = os.path.join('/'.join(bucket_name.split('/')[1:]), - os.path.basename(p)) - else: - name = os.path.basename(p) - logging.debug("Using key: {}".format(name)) - if bucket.get_key(name) is None or overwrite: - logging.info("Uploading file {}".format(name)) - k = Key(bucket) - k.key = name - if overwrite: - n = k.set_contents_from_filename(p, replace=True) - else: - n = k.set_contents_from_filename(p, replace=False) - k.make_public() - else: - logging.warn("Not uploading file {}, as it already exists in the target bucket.".format(name)) - return True - -def go_list(vendor=False, relative=False): - """ - Return a list of packages - If vendor is False vendor package are not included - If relative is True the package prefix defined by PACKAGE_URL is stripped - """ - p = subprocess.Popen(["go", "list", "./..."], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = p.communicate() - packages = out.split('\n') - if packages[-1] == '': - packages = packages[:-1] - if not vendor: - non_vendor = [] - for p in packages: - if '/vendor/' not in p: - non_vendor.append(p) - packages = non_vendor - if relative: - relative_pkgs = [] - for p in packages: - r = p.replace(PACKAGE_URL, '.') - if r != '.': - relative_pkgs.append(r) - packages = relative_pkgs - return packages - -def build(version=None, - platform=None, - arch=None, - nightly=False, - race=False, - clean=False, - outdir=".", - tags=[], - static=False): - """Build each target for the specified architecture and platform. - """ - logging.info("Starting build for {}/{}...".format(platform, arch)) - logging.info("Using Go version: {}".format(get_go_version())) - logging.info("Using git branch: {}".format(get_current_branch())) - logging.info("Using git commit: {}".format(get_current_commit())) - if static: - logging.info("Using statically-compiled output.") - if race: - logging.info("Race is enabled.") - if len(tags) > 0: - logging.info("Using build tags: {}".format(','.join(tags))) - - logging.info("Sending build output to: {}".format(outdir)) - if not os.path.exists(outdir): - os.makedirs(outdir) - elif clean and outdir != '/' and outdir != ".": - logging.info("Cleaning build directory '{}' before building.".format(outdir)) - shutil.rmtree(outdir) - os.makedirs(outdir) - - logging.info("Using version '{}' for build.".format(version)) - - for target, path in targets.items(): - logging.info("Building target: {}".format(target)) - build_command = "" - - # Handle static binary output - if static is True or "static_" in arch: - if "static_" in arch: - static = True - arch = arch.replace("static_", "") - build_command += "CGO_ENABLED=0 " - - # Handle variations in architecture output - if arch == "i386" or arch == "i686": - arch = "386" - elif "arm" in arch: - arch = "arm" - build_command += "GOOS={} GOARCH={} ".format(platform, arch) - - if "arm" in arch: - if arch == "armel": - build_command += "GOARM=5 " - elif arch == "armhf" or arch == "arm": - build_command += "GOARM=6 " - elif arch == "arm64": - # TODO(rossmcdonald) - Verify this is the correct setting for arm64 - build_command += "GOARM=7 " - else: - logging.error("Invalid ARM architecture specified: {}".format(arch)) - logging.error("Please specify either 'armel', 'armhf', or 'arm64'.") - return False - if platform == 'windows': - target = target + '.exe' - build_command += "go build -o {} ".format(os.path.join(outdir, target)) - if race: - build_command += "-race " - if len(tags) > 0: - build_command += "-tags {} ".format(','.join(tags)) - if "1.4" in get_go_version(): - if static: - build_command += "-ldflags=\"-s -X main.version {} -X main.commit {}\" ".format(version, - get_current_commit()) - else: - build_command += "-ldflags=\"-X main.version {} -X main.commit {}\" ".format(version, - get_current_commit()) - - else: - # Starting with Go 1.5, the linker flag arguments changed to 'name=value' from 'name value' - if static: - build_command += "-ldflags=\"-s -X main.version={} -X main.commit={}\" ".format(version, - get_current_commit()) - else: - build_command += "-ldflags=\"-X main.version={} -X main.commit={}\" ".format(version, - get_current_commit()) - if static: - build_command += "-a -installsuffix cgo " - build_command += path - start_time = datetime.utcnow() - run(build_command, shell=True, print_output=True) - end_time = datetime.utcnow() - logging.info("Time taken: {}s".format((end_time - start_time).total_seconds())) - return True - -def generate_sha256_from_file(path): - """Generate SHA256 signature based on the contents of the file at path. - """ - m = hashlib.sha256() - with open(path, 'rb') as f: - for chunk in iter(lambda: f.read(4096), b""): - m.update(chunk) - return m.hexdigest() - -def generate_md5_from_file(path): - """Generate MD5 signature based on the contents of the file at path. - """ - m = hashlib.md5() - with open(path, 'rb') as f: - for chunk in iter(lambda: f.read(4096), b""): - m.update(chunk) - return m.hexdigest() - -def generate_sig_from_file(path): - """Generate a detached GPG signature from the file at path. - """ - logging.debug("Generating GPG signature for file: {}".format(path)) - gpg_path = check_path_for('gpg') - if gpg_path is None: - logging.warn("gpg binary not found on path! Skipping signature creation.") - return False - if os.environ.get("GNUPG_HOME") is not None: - run('gpg --homedir {} --armor --yes --detach-sign {}'.format(os.environ.get("GNUPG_HOME"), path)) - else: - run('gpg --armor --detach-sign --yes {}'.format(path)) - return True - -def package(build_output, pkg_name, version, nightly=False, iteration=1, static=False, release=False): - """Package the output of the build process. - """ - outfiles = [] - tmp_build_dir = create_temp_dir() - logging.debug("Packaging for build output: {}".format(build_output)) - logging.info("Using temporary directory: {}".format(tmp_build_dir)) - try: - for platform in build_output: - # Create top-level folder displaying which platform (linux, etc) - os.makedirs(os.path.join(tmp_build_dir, platform)) - for arch in build_output[platform]: - logging.info("Creating packages for {}/{}".format(platform, arch)) - # Create second-level directory displaying the architecture (amd64, etc) - current_location = build_output[platform][arch] - - # Create directory tree to mimic file system of package - build_root = os.path.join(tmp_build_dir, - platform, - arch, - '{}-{}-{}'.format(PACKAGE_NAME, version, iteration)) - os.makedirs(build_root) - - # Copy packaging scripts to build directory - if platform == "windows": - # For windows and static builds, just copy - # binaries to root of package (no other scripts or - # directories) - package_scripts(build_root, config_only=True, windows=True) - elif static or "static_" in arch: - package_scripts(build_root, config_only=True) - else: - create_package_fs(build_root) - package_scripts(build_root) - - for binary in targets: - # Copy newly-built binaries to packaging directory - if platform == 'windows': - binary = binary + '.exe' - if platform == 'windows' or static or "static_" in arch: - # Where the binary should go in the package filesystem - to = os.path.join(build_root, binary) - # Where the binary currently is located - fr = os.path.join(current_location, binary) - else: - # Where the binary currently is located - fr = os.path.join(current_location, binary) - # Where the binary should go in the package filesystem - to = os.path.join(build_root, INSTALL_ROOT_DIR[1:], binary) - shutil.copy(fr, to) - - for package_type in supported_packages[platform]: - # Package the directory structure for each package type for the platform - logging.debug("Packaging directory '{}' as '{}'.".format(build_root, package_type)) - name = pkg_name - # Reset version, iteration, and current location on each run - # since they may be modified below. - package_version = version - package_iteration = iteration - if "static_" in arch: - # Remove the "static_" from the displayed arch on the package - package_arch = arch.replace("static_", "") - else: - package_arch = arch - if not release and not nightly: - # For non-release builds, just use the commit hash as the version - package_version = "{}~{}".format(version, - get_current_commit(short=True)) - package_iteration = "0" - package_build_root = build_root - current_location = build_output[platform][arch] - - if package_type in ['zip', 'tar']: - # For tars and zips, start the packaging one folder above - # the build root (to include the package name) - package_build_root = os.path.join('/', '/'.join(build_root.split('/')[:-1])) - if nightly: - if static or "static_" in arch: - name = '{}-static-nightly_{}_{}'.format(name, - platform, - package_arch) - else: - name = '{}-nightly_{}_{}'.format(name, - platform, - package_arch) - else: - if static or "static_" in arch: - name = '{}-{}-static_{}_{}'.format(name, - package_version, - platform, - package_arch) - else: - name = '{}-{}_{}_{}'.format(name, - package_version, - platform, - package_arch) - current_location = os.path.join(os.getcwd(), current_location) - if package_type == 'tar': - tar_command = "cd {} && tar -cvzf {}.tar.gz --owner=root ./*".format(package_build_root, name) - run(tar_command, shell=True, print_output=True) - run("mv {}.tar.gz {}".format(os.path.join(package_build_root, name), current_location), shell=True) - outfile = os.path.join(current_location, name + ".tar.gz") - outfiles.append(outfile) - elif package_type == 'zip': - zip_command = "cd {} && zip -r {}.zip ./*".format(package_build_root, name) - run(zip_command, shell=True, print_output=True) - run("mv {}.zip {}".format(os.path.join(package_build_root, name), current_location), shell=True) - outfile = os.path.join(current_location, name + ".zip") - outfiles.append(outfile) - elif package_type not in ['zip', 'tar'] and static or "static_" in arch: - logging.info("Skipping package type '{}' for static builds.".format(package_type)) - else: - fpm_command = "fpm {} --name {} -a {} -t {} --version {} --iteration {} -C {} -p {} ".format( - fpm_common_args, - name, - package_arch, - package_type, - package_version, - package_iteration, - package_build_root, - current_location) - if package_type == "rpm": - fpm_command += "--depends coreutils --depends shadow-utils" - # TODO: Check for changelog - # elif package_type == "deb": - # fpm_command += "--deb-changelog {} ".format(os.path.join(os.getcwd(), "CHANGELOG.md")) - out = run(fpm_command, shell=True) - matches = re.search(':path=>"(.*)"', out) - outfile = None - if matches is not None: - outfile = matches.groups()[0] - if outfile is None: - logging.warn("Could not determine output from packaging output!") - else: - if nightly: - # TODO: check if this is correct - # if package_type == 'rpm': - # # rpm's convert any dashes to underscores - # package_version = package_version.replace("-", "_") - # logging.debug("Changing package output version from {} to {} for RPM.".format(version, package_version)) - # Strip nightly version from package name - new_outfile = outfile.replace("{}-{}".format(package_version, package_iteration), "nightly") - os.rename(outfile, new_outfile) - outfile = new_outfile - else: - if package_type == 'rpm': - # rpm's convert any dashes to underscores - package_version = package_version.replace("-", "_") - logging.debug("Changing package output version from {} to {} for RPM.".format(version, package_version)) - new_outfile = outfile.replace("{}-{}".format(package_version, package_iteration), package_version) - os.rename(outfile, new_outfile) - outfile = new_outfile - outfiles.append(os.path.join(os.getcwd(), outfile)) - logging.debug("Produced package files: {}".format(outfiles)) - return outfiles - finally: - pass - # Cleanup - # shutil.rmtree(tmp_build_dir) - -def main(args): - global PACKAGE_NAME - - if args.release and args.nightly: - logging.error("Cannot be both a nightly and a release.") - return 1 - - if args.nightly: - args.version = increment_minor_version(args.version) - args.version = "{}~n{}".format(args.version, - datetime.utcnow().strftime("%Y%m%d%H%M")) - args.iteration = 0 - - # Pre-build checks - check_environ() - if not check_prereqs(): - return 1 - if args.build_tags is None: - args.build_tags = [] - else: - args.build_tags = args.build_tags.split(',') - - orig_commit = get_current_commit(short=True) - orig_branch = get_current_branch() - - if args.platform not in supported_builds and args.platform != 'all': - logging.error("Invalid build platform: {}".format(args.platform)) - return 1 - - build_output = {} - - if args.branch != orig_branch and args.commit != orig_commit: - logging.error("Can only specify one branch or commit to build from.") - return 1 - elif args.branch != orig_branch: - logging.info("Moving to git branch: {}".format(args.branch)) - run("git checkout {}".format(args.branch), print_output=True) - elif args.commit != orig_commit: - logging.info("Moving to git commit: {}".format(args.commit)) - run("git checkout {}".format(args.commit), print_output=True) - - if args.clean: - if not make_clean(): - return 1 - - if not args.no_get: - if not go_get(args.branch, update=args.update, no_uncommitted=args.no_uncommitted): - return 1 - - if args.generate: - if not run_generate(): - return 1 - - if args.test: - if not run_tests(args.race, args.parallel, args.timeout, args.no_vet): - return 1 - - if args.no_build: - return 0 - - platforms = [] - single_build = True - if args.platform == 'all': - platforms = supported_builds.keys() - single_build = False - else: - platforms = [args.platform] - - for platform in platforms: - build_output.update( { platform : {} } ) - archs = [] - if args.arch == "all": - single_build = False - archs = supported_builds.get(platform) - else: - archs = [args.arch] - - for arch in archs: - od = args.outdir - if not single_build: - od = os.path.join(args.outdir, platform, arch) - if not build(version=args.version, - platform=platform, - arch=arch, - nightly=args.nightly, - race=args.race, - clean=args.clean, - outdir=od, - tags=args.build_tags, - static=args.static): - return 1 - build_output.get(platform).update( { arch : od } ) - - # Build packages - if args.package: - if not check_path_for("fpm"): - logging.error("FPM ruby gem required for packaging. Stopping.") - return 1 - packages = package(build_output, - args.name, - args.version, - nightly=args.nightly, - iteration=args.iteration, - static=args.static, - release=args.release) - if args.sign: - logging.debug("Generating GPG signatures for packages: {}".format(packages)) - sigs = [] # retain signatures so they can be uploaded with packages - for p in packages: - if generate_sig_from_file(p): - sigs.append(p + '.asc') - else: - logging.error("Creation of signature for package [{}] failed!".format(p)) - return 1 - packages += sigs - if args.upload: - logging.debug("Files staged for upload: {}".format(packages)) - if args.nightly: - args.upload_overwrite = True - if not upload_packages(packages, bucket_name=args.bucket, overwrite=args.upload_overwrite): - return 1 - package_output = {} - for p in packages: - p_name = p.split('/')[-1:][0] - if ".asc" in p_name: - # Skip public keys - continue - - arch = None - type = None - regex = None - nice_name = None - if ".deb" in p_name: - type = "ubuntu" - nice_name = "Ubuntu" - regex = r"^.+_(.+)\.deb$" - elif ".rpm" in p_name: - type = "centos" - nice_name = "CentOS" - regex = r"^.+\.(.+)\.rpm$" - elif ".tar.gz" in p_name: - if "linux" in p_name: - if "static" in p_name: - type = "linux_static" - nice_name = "Linux Static" - else: - type = "linux" - nice_name = "Linux" - elif "darwin" in p_name: - type = "darwin" - nice_name = "Mac OS X" - regex = r"^.+_(.+)\.tar.gz$" - elif ".zip" in p_name: - if "windows" in p_name: - type = "windows" - nice_name = "Windows" - regex = r"^.+_(.+)\.zip$" - - if regex is None or type is None: - logging.error("Could not determine package type for: {}".format(p)) - return 1 - match = re.search(regex, p_name) - arch = match.groups()[0] - if arch is None: - logging.error("Could not determine arch for: {}".format(p)) - return 1 - if arch == "x86_64": - arch = "amd64" - elif arch == "x86_32": - arch = "i386" - package_name = str(arch) + "_" + str(type) - package_output[package_name] = { - "sha256": generate_sha256_from_file(p), - "md5": generate_md5_from_file(p), - "filename": p_name, - "name": nice_name, - "link": "https://dl.influxdata.com/chronograf/releases/" + p_name.rsplit('/', 1)[-1], - } - - # Print the downloads in Markdown format for the release - if args.release: - lines = [] - for package_name, v in package_output.items(): - line = v['name'] + " | [" + v['filename'] +"](" + v['link'] + ") | `" + v['sha256'] + "`" - lines.append(line) - lines.sort() - - print ("## Docker") - print("`docker pull quay.io/influxdb/chronograf:"+get_current_version_tag() + "`") - print("") - print("## Packages") - print("") - print("Platform | Package | SHA256") - print("--- | --- | ---") - for line in lines: - print(line) - package_output["version"] = args.version - logging.info(json.dumps(package_output, sort_keys=True, indent=4)) - if orig_branch != get_current_branch(): - logging.info("Moving back to original git branch: {}".format(orig_branch)) - run("git checkout {}".format(orig_branch), print_output=True) - - return 0 - -if __name__ == '__main__': - LOG_LEVEL = logging.INFO - if '--debug' in sys.argv[1:]: - LOG_LEVEL = logging.DEBUG - log_format = '[%(levelname)s] %(funcName)s: %(message)s' - logging.basicConfig(stream=sys.stdout, - level=LOG_LEVEL, - format=log_format) - - parser = argparse.ArgumentParser(description='InfluxDB build and packaging script.') - parser.add_argument('--verbose','-v','--debug', - action='store_true', - help='Use debug output') - parser.add_argument('--outdir', '-o', - metavar='', - default='./build/', - type=os.path.abspath, - help='Output directory') - parser.add_argument('--name', '-n', - metavar='', - default=PACKAGE_NAME, - type=str, - help='Name to use for package name (when package is specified)') - parser.add_argument('--arch', - metavar='', - type=str, - default=get_system_arch(), - help='Target architecture for build output') - parser.add_argument('--platform', - metavar='', - type=str, - default=get_system_platform(), - help='Target platform for build output') - parser.add_argument('--branch', - metavar='', - type=str, - default=get_current_branch(), - help='Build from a specific branch') - parser.add_argument('--commit', - metavar='', - type=str, - default=get_current_commit(short=True), - help='Build from a specific commit') - parser.add_argument('--version', - metavar='', - type=str, - default=get_current_version(), - help='Version information to apply to build output (ex: 0.12.0)') - parser.add_argument('--iteration', - metavar='', - type=str, - default="1", - help='Package iteration to apply to build output (defaults to 1)') - parser.add_argument('--stats', - action='store_true', - help='Emit build metrics (requires InfluxDB Python client)') - parser.add_argument('--stats-server', - metavar='', - type=str, - help='Send build stats to InfluxDB using provided hostname and port') - parser.add_argument('--stats-db', - metavar='', - type=str, - help='Send build stats to InfluxDB using provided database name') - parser.add_argument('--nightly', - action='store_true', - help='Mark build output as nightly build (will incremement the minor version)') - parser.add_argument('--update', - action='store_true', - help='Update build dependencies prior to building') - parser.add_argument('--package', - action='store_true', - help='Package binary output') - parser.add_argument('--release', - action='store_true', - help='Mark build output as release') - parser.add_argument('--clean', - action='store_true', - help='Clean output directory before building') - parser.add_argument('--no-get', - action='store_true', - help='Do not retrieve pinned dependencies when building') - parser.add_argument('--no-uncommitted', - action='store_true', - help='Fail if uncommitted changes exist in the working directory') - parser.add_argument('--upload', - action='store_true', - help='Upload output packages to AWS S3') - parser.add_argument('--upload-overwrite','-w', - action='store_true', - help='Upload output packages to AWS S3') - parser.add_argument('--bucket', - metavar='', - type=str, - default=DEFAULT_BUCKET, - help='Destination bucket for uploads') - parser.add_argument('--generate', - action='store_true', - default=True, - help='Run "go generate" before building') - parser.add_argument('--build-tags', - metavar='', - help='Optional build tags to use for compilation') - parser.add_argument('--static', - action='store_true', - help='Create statically-compiled binary output') - parser.add_argument('--sign', - action='store_true', - help='Create GPG detached signatures for packages (when package is specified)') - parser.add_argument('--test', - action='store_true', - help='Run tests (does not produce build output)') - parser.add_argument('--no-vet', - action='store_true', - help='Do not run "go vet" when running tests') - parser.add_argument('--race', - action='store_true', - help='Enable race flag for build output') - parser.add_argument('--parallel', - metavar='', - type=int, - help='Number of tests to run simultaneously') - parser.add_argument('--timeout', - metavar='', - type=str, - help='Timeout for tests before failing') - parser.add_argument('--no-build', - action='store_true', - help='Dont build anything.') - args = parser.parse_args() - print_banner() - sys.exit(main(args)) diff --git a/chronograf/etc/config.sample.toml b/chronograf/etc/config.sample.toml deleted file mode 100644 index e575d4ef6f3..00000000000 --- a/chronograf/etc/config.sample.toml +++ /dev/null @@ -1 +0,0 @@ -# TODO: wire up configuration files \ No newline at end of file diff --git a/chronograf/etc/licenses.sh b/chronograf/etc/licenses.sh deleted file mode 100644 index 5cab4b66fc6..00000000000 --- a/chronograf/etc/licenses.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -for a in `gdl -no-vendored -test -repo ./... | awk 'NR>1 {print $5}'`; do echo \[\]\($a/blob/master/\) ; done -nlf -c |awk -F, '{printf "%s %s \[%s\]\(%s\)\n", $1, $2, $5, $4}' diff --git a/chronograf/etc/scripts/chronograf.service b/chronograf/etc/scripts/chronograf.service deleted file mode 100644 index 272429f865d..00000000000 --- a/chronograf/etc/scripts/chronograf.service +++ /dev/null @@ -1,21 +0,0 @@ -# If you modify this, please also make sure to edit init.sh - -[Unit] -Description=Open source monitoring and visualization UI for the entire TICK stack. -Documentation="https://www.influxdata.com/time-series-platform/chronograf/" -After=network-online.target - -[Service] -User=chronograf -Group=chronograf -Environment="HOST=0.0.0.0" -Environment="PORT=8888" -Environment="BOLT_PATH=/var/lib/chronograf/chronograf-v1.db" -Environment="CANNED_PATH=/usr/share/chronograf/canned" -EnvironmentFile=-/etc/default/chronograf -ExecStart=/usr/bin/chronograf $CHRONOGRAF_OPTS -KillMode=control-group -Restart=on-failure - -[Install] -WantedBy=multi-user.target diff --git a/chronograf/etc/scripts/docker/build.sh b/chronograf/etc/scripts/docker/build.sh deleted file mode 100755 index c40a1a236bf..00000000000 --- a/chronograf/etc/scripts/docker/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -x -docker_tag="chronograf-$(date +%Y%m%d)" - -docker build --rm=false -f etc/Dockerfile_build -t builder:$docker_tag . -docker tag builder:$docker_tag quay.io/influxdb/builder:$docker_tag - -docker push quay.io/influxdb/builder:$docker_tag diff --git a/chronograf/etc/scripts/docker/pull.sh b/chronograf/etc/scripts/docker/pull.sh deleted file mode 100755 index dfe72f531e1..00000000000 --- a/chronograf/etc/scripts/docker/pull.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# -# Pull the required build image from quay.io. -# - -if [[ -z "$DOCKER_TAG" ]]; then - echo "Please specify a tag to pull from with the DOCKER_TAG env variable." - exit 1 -fi - -docker pull quay.io/influxdb/builder:$DOCKER_TAG diff --git a/chronograf/etc/scripts/docker/run.sh b/chronograf/etc/scripts/docker/run.sh deleted file mode 100755 index 025952aec11..00000000000 --- a/chronograf/etc/scripts/docker/run.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# -# Pass all CLI arguments to Chronograf builder Docker image (passing -# them to the build scripts) -# -# WARNING: This script passes your SSH and AWS credentials within the -# Docker image, so use with caution. -# - -set -e - -# Default SSH key to $HOME/.ssh/id_rsa if not set -test -z $SSH_KEY_PATH && SSH_KEY_PATH="$HOME/.ssh/id_rsa" -echo "Using SSH key located at: $SSH_KEY_PATH" - -# Default docker tag if not specified -test -z "$DOCKER_TAG" && DOCKER_TAG="chronograf-20161121" - -docker run \ - -e AWS_ACCESS_KEY_ID \ - -e AWS_SECRET_ACCESS_KEY \ - -v $SSH_KEY_PATH:/root/.ssh/id_rsa \ - -v ~/.ssh/known_hosts:/root/.ssh/known_hosts \ - -v $(pwd):/root/go/src/github.com/influxdata/influxdb/chronograf \ - quay.io/influxdb/builder:$DOCKER_TAG \ - "$@" diff --git a/chronograf/etc/scripts/init.sh b/chronograf/etc/scripts/init.sh deleted file mode 100755 index 6b52743f016..00000000000 --- a/chronograf/etc/scripts/init.sh +++ /dev/null @@ -1,112 +0,0 @@ -#!/bin/bash -### BEGIN INIT INFO -# Provides: chronograf -# Required-Start: $local_fs $network $named $time $syslog -# Required-Stop: $local_fs $network $named $time $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Start the Chronograf service at boot time -### END INIT INFO - -# If you modify this, please make sure to also edit chronograf.service - -# Script to execute when starting -SCRIPT="/usr/bin/chronograf" -export HOST="0.0.0.0" -export PORT="8888" -export BOLT_PATH="/var/lib/chronograf/chronograf-v1.db" -export CANNED_PATH="/usr/share/chronograf/canned" -# Options to pass to the script on startup -. /etc/default/chronograf -SCRIPT_OPTS="${CHRONOGRAF_OPTS}" - -# User to run the process under -RUNAS=chronograf - -# PID file for process -PIDFILE=/var/run/chronograf.pid -# Where to redirect logging to -LOGFILE=/var/log/chronograf/chronograf.log - -start() { - if [[ -f $PIDFILE ]]; then - # PIDFILE exists - if kill -0 $(cat $PIDFILE) &>/dev/null; then - # PID up, service running - echo '[OK] Service already running.' >&2 - return 0 - fi - fi - local CMD="$SCRIPT $SCRIPT_OPTS 1>>\"$LOGFILE\" 2>&1 & echo \$!" - su -s /bin/sh -c "$CMD" $RUNAS > "$PIDFILE" - if [[ -f $PIDFILE ]]; then - # PIDFILE exists - if kill -0 $(cat $PIDFILE) &>/dev/null; then - # PID up, service running - echo '[OK] Service successfully started.' >&2 - return 0 - fi - fi - echo '[ERROR] Could not start service.' >&2 - return 1 -} - -status() { - if [[ -f $PIDFILE ]]; then - # PIDFILE exists - if ps -p $(cat $PIDFILE) &>/dev/null; then - # PID up, service running - echo '[OK] Service running.' >&2 - return 0 - fi - fi - echo '[ERROR] Service not running.' >&2 - return 1 -} - -stop() { - if [[ -f $PIDFILE ]]; then - # PIDFILE still exists - if kill -0 $(cat $PIDFILE) &>/dev/null; then - # PID still up - kill -15 $(cat $PIDFILE) &>/dev/null && rm -f "$PIDFILE" &>/dev/null - if [[ "$?" = "0" ]]; then - # Successful stop - echo '[OK] Service stopped.' >&2 - return 0 - else - # Unsuccessful stop - echo '[ERROR] Could not stop service.' >&2 - return 1 - fi - fi - fi - echo "[OK] Service already stopped." - return 0 -} - -case "$1" in - start) - if [[ "$UID" != "0" ]]; then - echo "[ERROR] Permission denied." - exit 1 - fi - start - ;; - status) - status - ;; - stop) - if [[ "$UID" != "0" ]]; then - echo "[ERROR] Permission denied." - exit 1 - fi - stop - ;; - restart) - stop - start - ;; - *) - echo "Usage: $0 {start|status|stop|restart}" - esac diff --git a/chronograf/etc/scripts/logrotate b/chronograf/etc/scripts/logrotate deleted file mode 100644 index f172c0dff69..00000000000 --- a/chronograf/etc/scripts/logrotate +++ /dev/null @@ -1,9 +0,0 @@ -/var/log/chronograf/chronograf.log { - daily - rotate 7 - missingok - dateext - copytruncate - compress - notifempty -} diff --git a/chronograf/etc/scripts/post-install.sh b/chronograf/etc/scripts/post-install.sh deleted file mode 100644 index 0d570c53b8e..00000000000 --- a/chronograf/etc/scripts/post-install.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -BIN_DIR=/usr/bin -DATA_DIR=/var/lib/chronograf -LOG_DIR=/var/log/chronograf -SCRIPT_DIR=/usr/lib/chronograf/scripts -LOGROTATE_DIR=/etc/logrotate.d - -function install_init { - cp -f $SCRIPT_DIR/init.sh /etc/init.d/chronograf - chmod +x /etc/init.d/chronograf -} - -function install_systemd { - # Remove any existing symlinks - rm -f /etc/systemd/system/chronograf.service - - cp -f $SCRIPT_DIR/chronograf.service /lib/systemd/system/chronograf.service - systemctl enable chronograf || true - systemctl daemon-reload || true -} - -function install_update_rcd { - update-rc.d chronograf defaults -} - -function install_chkconfig { - chkconfig --add chronograf -} - -id chronograf &>/dev/null -if [[ $? -ne 0 ]]; then - useradd --system -U -M chronograf -s /bin/false -d $DATA_DIR -fi - -test -d $LOG_DIR || mkdir -p $DATA_DIR -test -d $DATA_DIR || mkdir -p $DATA_DIR -chown -R -L chronograf:chronograf $LOG_DIR -chown -R -L chronograf:chronograf $DATA_DIR -chmod 755 $LOG_DIR -chmod 755 $DATA_DIR - -# Remove legacy symlink, if it exists -if [[ -L /etc/init.d/chronograf ]]; then - rm -f /etc/init.d/chronograf -fi - -# Add defaults file, if it doesn't exist -if [[ ! -f /etc/default/chronograf ]]; then - touch /etc/default/chronograf -fi - -# Distribution-specific logic -if [[ -f /etc/redhat-release ]]; then - # RHEL-variant logic - which systemctl &>/dev/null - if [[ $? -eq 0 ]]; then - install_systemd - else - # Assuming sysv - install_init - install_chkconfig - fi -elif [[ -f /etc/debian_version ]]; then - # Debian/Ubuntu logic - which systemctl &>/dev/null - if [[ $? -eq 0 ]]; then - install_systemd - systemctl restart chronograf || echo "WARNING: systemd not running." - else - # Assuming sysv - install_init - install_update_rcd - invoke-rc.d chronograf restart - fi -elif [[ -f /etc/os-release ]]; then - source /etc/os-release - if [[ $ID = "amzn" ]]; then - # Amazon Linux logic - install_init - install_chkconfig - fi -fi diff --git a/chronograf/etc/scripts/post-uninstall.sh b/chronograf/etc/scripts/post-uninstall.sh deleted file mode 100644 index 7fee6e4c4d3..00000000000 --- a/chronograf/etc/scripts/post-uninstall.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/bash - -function disable_systemd { - systemctl disable chronograf - rm -f /lib/systemd/system/chronograf.service -} - -function disable_update_rcd { - update-rc.d -f chronograf remove - rm -f /etc/init.d/chronograf -} - -function disable_chkconfig { - chkconfig --del chronograf - rm -f /etc/init.d/chronograf -} - -if [[ -f /etc/redhat-release ]]; then - # RHEL-variant logic - if [[ "$1" = "0" ]]; then - # chronograf is no longer installed, remove from init system - rm -f /etc/default/chronograf - - which systemctl &>/dev/null - if [[ $? -eq 0 ]]; then - disable_systemd - else - # Assuming sysv - disable_chkconfig - fi - fi -elif [[ -f /etc/lsb-release ]]; then - # Debian/Ubuntu logic - if [[ "$1" != "upgrade" ]]; then - # Remove/purge - rm -f /etc/default/chronograf - - which systemctl &>/dev/null - if [[ $? -eq 0 ]]; then - disable_systemd - else - # Assuming sysv - disable_update_rcd - fi - fi -elif [[ -f /etc/os-release ]]; then - source /etc/os-release - if [[ $ID = "amzn" ]]; then - # Amazon Linux logic - if [[ "$1" = "0" ]]; then - # chronograf is no longer installed, remove from init system - rm -f /etc/default/chronograf - disable_chkconfig - fi - fi -fi diff --git a/chronograf/filestore/apps.go b/chronograf/filestore/apps.go deleted file mode 100644 index 902808f6613..00000000000 --- a/chronograf/filestore/apps.go +++ /dev/null @@ -1,205 +0,0 @@ -package filestore - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/pkg/fs" -) - -// AppExt is the the file extension searched for in the directory for layout files -const AppExt = ".json" - -// Apps are canned JSON layouts. Implements LayoutsStore. -type Apps struct { - Dir string // Dir is the directory contained the pre-canned applications. - Load func(string) (chronograf.Layout, error) // Load loads string name and return a Layout - Filename func(string, chronograf.Layout) string // Filename takes dir and layout and returns loadable file - Create func(string, chronograf.Layout) error // Create will write layout to file. - ReadDir func(dirname string) ([]os.FileInfo, error) // ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename. - Remove func(name string) error // Remove file - IDs chronograf.ID // IDs generate unique ids for new application layouts - Logger chronograf.Logger -} - -// NewApps constructs a layout store wrapping a file system directory -func NewApps(dir string, ids chronograf.ID, logger chronograf.Logger) chronograf.LayoutsStore { - return &Apps{ - Dir: dir, - Load: loadFile, - Filename: fileName, - Create: createLayout, - ReadDir: ioutil.ReadDir, - Remove: os.Remove, - IDs: ids, - Logger: logger, - } -} - -func fileName(dir string, layout chronograf.Layout) string { - base := fmt.Sprintf("%s%s", layout.Measurement, AppExt) - return path.Join(dir, base) -} - -func loadFile(name string) (chronograf.Layout, error) { - octets, err := ioutil.ReadFile(name) - if err != nil { - return chronograf.Layout{}, chronograf.ErrLayoutNotFound - } - var layout chronograf.Layout - if err = json.Unmarshal(octets, &layout); err != nil { - return chronograf.Layout{}, chronograf.ErrLayoutInvalid - } - return layout, nil -} - -func createLayout(file string, layout chronograf.Layout) error { - h, err := fs.CreateFile(file) - if err != nil { - return err - } - defer h.Close() - if octets, err := json.MarshalIndent(layout, " ", " "); err != nil { - return chronograf.ErrLayoutInvalid - } else if _, err := h.Write(octets); err != nil { - return err - } - - return nil -} - -// All returns all layouts from the directory -func (a *Apps) All(ctx context.Context) ([]chronograf.Layout, error) { - files, err := a.ReadDir(a.Dir) - if err != nil { - return nil, err - } - - layouts := []chronograf.Layout{} - for _, file := range files { - if path.Ext(file.Name()) != AppExt { - continue - } - if layout, err := a.Load(path.Join(a.Dir, file.Name())); err != nil { - continue // We want to load all files we can. - } else { - layouts = append(layouts, layout) - } - } - return layouts, nil -} - -// Add creates a new layout within the directory -func (a *Apps) Add(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error) { - var err error - layout.ID, err = a.IDs.Generate() - if err != nil { - a.Logger. - WithField("component", "apps"). - Error("Unable to generate ID") - return chronograf.Layout{}, err - } - file := a.Filename(a.Dir, layout) - if err = a.Create(file, layout); err != nil { - if err == chronograf.ErrLayoutInvalid { - a.Logger. - WithField("component", "apps"). - WithField("name", file). - Error("Invalid Layout: ", err) - } else { - a.Logger. - WithField("component", "apps"). - WithField("name", file). - Error("Unable to write layout:", err) - } - return chronograf.Layout{}, err - } - return layout, nil -} - -// Delete removes a layout file from the directory -func (a *Apps) Delete(ctx context.Context, layout chronograf.Layout) error { - _, file, err := a.idToFile(layout.ID) - if err != nil { - return err - } - - if err := a.Remove(file); err != nil { - a.Logger. - WithField("component", "apps"). - WithField("name", file). - Error("Unable to remove layout:", err) - return err - } - return nil -} - -// Get returns an app file from the layout directory -func (a *Apps) Get(ctx context.Context, ID string) (chronograf.Layout, error) { - l, file, err := a.idToFile(ID) - if err != nil { - return chronograf.Layout{}, err - } - - if err != nil { - if err == chronograf.ErrLayoutNotFound { - a.Logger. - WithField("component", "apps"). - WithField("name", file). - Error("Unable to read file") - } else if err == chronograf.ErrLayoutInvalid { - a.Logger. - WithField("component", "apps"). - WithField("name", file). - Error("File is not a layout") - } - return chronograf.Layout{}, err - } - return l, nil -} - -// Update replaces a layout from the file system directory -func (a *Apps) Update(ctx context.Context, layout chronograf.Layout) error { - l, _, err := a.idToFile(layout.ID) - if err != nil { - return err - } - - if err := a.Delete(ctx, l); err != nil { - return err - } - file := a.Filename(a.Dir, layout) - return a.Create(file, layout) -} - -// idToFile takes an id and finds the associated filename -func (a *Apps) idToFile(ID string) (chronograf.Layout, string, error) { - // Because the entire layout information is not known at this point, we need - // to try to find the name of the file through matching the ID in the layout - // content with the ID passed. - files, err := a.ReadDir(a.Dir) - if err != nil { - return chronograf.Layout{}, "", err - } - - for _, f := range files { - if path.Ext(f.Name()) != AppExt { - continue - } - file := path.Join(a.Dir, f.Name()) - layout, err := a.Load(file) - if err != nil { - return chronograf.Layout{}, "", err - } - if layout.ID == ID { - return layout, file, nil - } - } - - return chronograf.Layout{}, "", chronograf.ErrLayoutNotFound -} diff --git a/chronograf/filestore/apps_test.go b/chronograf/filestore/apps_test.go deleted file mode 100644 index f304c75f580..00000000000 --- a/chronograf/filestore/apps_test.go +++ /dev/null @@ -1,378 +0,0 @@ -package filestore_test - -import ( - "context" - "errors" - "os" - "path" - "path/filepath" - "reflect" - "sort" - "strconv" - "testing" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/filestore" -) - -func TestAll(t *testing.T) { - t.Parallel() - var tests = []struct { - Existing []chronograf.Layout - Err error - }{ - { - Existing: []chronograf.Layout{ - {ID: "1", - Application: "howdy", - }, - {ID: "2", - Application: "doody", - }, - }, - Err: nil, - }, - { - Existing: []chronograf.Layout{}, - Err: nil, - }, - { - Existing: nil, - Err: errors.New("error"), - }, - } - for i, test := range tests { - apps, _ := MockApps(test.Existing, test.Err) - layouts, err := apps.All(context.Background()) - if err != test.Err { - t.Errorf("Test %d: apps all error expected: %v; actual: %v", i, test.Err, err) - } - if !reflect.DeepEqual(layouts, test.Existing) { - t.Errorf("Test %d: Layouts should be equal; expected %v; actual %v", i, test.Existing, layouts) - } - } -} - -func TestAdd(t *testing.T) { - t.Parallel() - var tests = []struct { - Existing []chronograf.Layout - Add chronograf.Layout - ExpectedID string - Err error - }{ - { - Existing: []chronograf.Layout{ - {ID: "1", - Application: "howdy", - }, - {ID: "2", - Application: "doody", - }, - }, - Add: chronograf.Layout{ - Application: "newbie", - }, - ExpectedID: "3", - Err: nil, - }, - { - Existing: []chronograf.Layout{}, - Add: chronograf.Layout{ - Application: "newbie", - }, - ExpectedID: "1", - Err: nil, - }, - { - Existing: nil, - Add: chronograf.Layout{ - Application: "newbie", - }, - ExpectedID: "", - Err: errors.New("error"), - }, - } - for i, test := range tests { - apps, _ := MockApps(test.Existing, test.Err) - layout, err := apps.Add(context.Background(), test.Add) - if err != test.Err { - t.Errorf("Test %d: apps add error expected: %v; actual: %v", i, test.Err, err) - } - - if layout.ID != test.ExpectedID { - t.Errorf("Test %d: Layout ID should be equal; expected %s; actual %s", i, test.ExpectedID, layout.ID) - } - } -} - -func TestDelete(t *testing.T) { - t.Parallel() - var tests = []struct { - Existing []chronograf.Layout - DeleteID string - Expected map[string]chronograf.Layout - Err error - }{ - { - Existing: []chronograf.Layout{ - {ID: "1", - Application: "howdy", - }, - {ID: "2", - Application: "doody", - }, - }, - DeleteID: "1", - Expected: map[string]chronograf.Layout{ - "dir/2.json": {ID: "2", - Application: "doody", - }, - }, - Err: nil, - }, - { - Existing: []chronograf.Layout{}, - DeleteID: "1", - Expected: map[string]chronograf.Layout{}, - Err: chronograf.ErrLayoutNotFound, - }, - { - Existing: nil, - DeleteID: "1", - Expected: map[string]chronograf.Layout{}, - Err: errors.New("error"), - }, - } - for i, test := range tests { - apps, actual := MockApps(test.Existing, test.Err) - err := apps.Delete(context.Background(), chronograf.Layout{ID: test.DeleteID}) - if err != test.Err { - t.Errorf("Test %d: apps delete error expected: %v; actual: %v", i, test.Err, err) - } - if !reflect.DeepEqual(*actual, test.Expected) { - t.Errorf("Test %d: Layouts should be equal; expected %v; actual %v", i, test.Expected, actual) - } - } -} - -func TestGet(t *testing.T) { - t.Parallel() - var tests = []struct { - Existing []chronograf.Layout - ID string - Expected chronograf.Layout - Err error - }{ - { - Existing: []chronograf.Layout{ - {ID: "1", - Application: "howdy", - }, - {ID: "2", - Application: "doody", - }, - }, - ID: "1", - Expected: chronograf.Layout{ - ID: "1", - Application: "howdy", - }, - Err: nil, - }, - { - Existing: []chronograf.Layout{}, - ID: "1", - Expected: chronograf.Layout{}, - Err: chronograf.ErrLayoutNotFound, - }, - { - Existing: nil, - ID: "1", - Expected: chronograf.Layout{}, - Err: chronograf.ErrLayoutNotFound, - }, - } - for i, test := range tests { - apps, _ := MockApps(test.Existing, test.Err) - layout, err := apps.Get(context.Background(), test.ID) - if err != test.Err { - t.Errorf("Test %d: Layouts get error expected: %v; actual: %v", i, test.Err, err) - } - if !reflect.DeepEqual(layout, test.Expected) { - t.Errorf("Test %d: Layouts should be equal; expected %v; actual %v", i, test.Expected, layout) - } - } -} - -func TestUpdate(t *testing.T) { - t.Parallel() - var tests = []struct { - Existing []chronograf.Layout - Update chronograf.Layout - Expected map[string]chronograf.Layout - Err error - }{ - { - Existing: []chronograf.Layout{ - {ID: "1", - Application: "howdy", - }, - {ID: "2", - Application: "doody", - }, - }, - Update: chronograf.Layout{ - ID: "1", - Application: "hello", - Measurement: "measurement", - }, - Expected: map[string]chronograf.Layout{ - "dir/1.json": {ID: "1", - Application: "hello", - Measurement: "measurement", - }, - "dir/2.json": {ID: "2", - Application: "doody", - }, - }, - Err: nil, - }, - { - Existing: []chronograf.Layout{}, - Update: chronograf.Layout{ - ID: "1", - }, - Expected: map[string]chronograf.Layout{}, - Err: chronograf.ErrLayoutNotFound, - }, - { - Existing: nil, - Update: chronograf.Layout{ - ID: "1", - }, - Expected: map[string]chronograf.Layout{}, - Err: chronograf.ErrLayoutNotFound, - }, - } - for i, test := range tests { - apps, actual := MockApps(test.Existing, test.Err) - err := apps.Update(context.Background(), test.Update) - if err != test.Err { - t.Errorf("Test %d: Layouts get error expected: %v; actual: %v", i, test.Err, err) - } - if !reflect.DeepEqual(*actual, test.Expected) { - t.Errorf("Test %d: Layouts should be equal; expected %v; actual %v", i, test.Expected, actual) - } - } -} - -type MockFileInfo struct { - name string -} - -func (m *MockFileInfo) Name() string { - return m.name -} - -func (m *MockFileInfo) Size() int64 { - return 0 -} - -func (m *MockFileInfo) Mode() os.FileMode { - return 0666 -} - -func (m *MockFileInfo) ModTime() time.Time { - return time.Now() -} - -func (m *MockFileInfo) IsDir() bool { - return false -} - -func (m *MockFileInfo) Sys() interface{} { - return nil -} - -type MockFileInfos []os.FileInfo - -func (m MockFileInfos) Len() int { return len(m) } -func (m MockFileInfos) Swap(i, j int) { m[i], m[j] = m[j], m[i] } -func (m MockFileInfos) Less(i, j int) bool { return m[i].Name() < m[j].Name() } - -type MockID struct { - id int -} - -func (m *MockID) Generate() (string, error) { - m.id++ - return strconv.Itoa(m.id), nil -} - -func MockApps(existing []chronograf.Layout, expected error) (filestore.Apps, *map[string]chronograf.Layout) { - layouts := map[string]chronograf.Layout{} - fileName := func(dir string, layout chronograf.Layout) string { - return path.Join(dir, layout.ID+".json") - } - dir := "dir" - for _, l := range existing { - layouts[fileName(dir, l)] = l - } - load := func(file string) (chronograf.Layout, error) { - if expected != nil { - return chronograf.Layout{}, expected - } - - l, ok := layouts[file] - if !ok { - return chronograf.Layout{}, chronograf.ErrLayoutNotFound - } - return l, nil - } - - create := func(file string, layout chronograf.Layout) error { - if expected != nil { - return expected - } - layouts[file] = layout - return nil - } - - readDir := func(dirname string) ([]os.FileInfo, error) { - if expected != nil { - return nil, expected - } - info := []os.FileInfo{} - for k := range layouts { - info = append(info, &MockFileInfo{filepath.Base(k)}) - } - sort.Sort(MockFileInfos(info)) - return info, nil - } - - remove := func(name string) error { - if expected != nil { - return expected - } - if _, ok := layouts[name]; !ok { - return chronograf.ErrLayoutNotFound - } - delete(layouts, name) - return nil - } - - return filestore.Apps{ - Dir: dir, - Load: load, - Filename: fileName, - Create: create, - ReadDir: readDir, - Remove: remove, - IDs: &MockID{ - id: len(existing), - }, - Logger: &chronograf.NoopLogger{}, - }, &layouts -} diff --git a/chronograf/filestore/dashboards.go b/chronograf/filestore/dashboards.go deleted file mode 100644 index e3eae92dbae..00000000000 --- a/chronograf/filestore/dashboards.go +++ /dev/null @@ -1,211 +0,0 @@ -package filestore - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path" - "strconv" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/pkg/fs" -) - -// DashExt is the the file extension searched for in the directory for dashboard files -const DashExt = ".dashboard" - -var _ chronograf.DashboardsStore = &Dashboards{} - -// Dashboards are JSON dashboards stored in the filesystem -type Dashboards struct { - Dir string // Dir is the directory containing the dashboards. - Load func(string, interface{}) error // Load loads string name and dashboard passed in as interface - Create func(string, interface{}) error // Create will write dashboard to file. - ReadDir func(dirname string) ([]os.FileInfo, error) // ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename. - Remove func(name string) error // Remove file - IDs chronograf.ID // IDs generate unique ids for new dashboards - Logger chronograf.Logger -} - -// NewDashboards constructs a dashboard store wrapping a file system directory -func NewDashboards(dir string, ids chronograf.ID, logger chronograf.Logger) chronograf.DashboardsStore { - return &Dashboards{ - Dir: dir, - Load: load, - Create: create, - ReadDir: ioutil.ReadDir, - Remove: os.Remove, - IDs: ids, - Logger: logger, - } -} - -func dashboardFile(dir string, dashboard chronograf.Dashboard) string { - base := fmt.Sprintf("%s%s", dashboard.Name, DashExt) - return path.Join(dir, base) -} - -func load(name string, resource interface{}) error { - octets, err := templatedFromEnv(name) - if err != nil { - return fmt.Errorf("resource %s not found", name) - } - - return json.Unmarshal(octets, resource) -} - -func create(file string, resource interface{}) error { - h, err := fs.CreateFile(file) - if err != nil { - return err - } - defer h.Close() - - octets, err := json.MarshalIndent(resource, " ", " ") - if err != nil { - return err - } - - _, err = h.Write(octets) - return err -} - -// All returns all dashboards from the directory -func (d *Dashboards) All(ctx context.Context) ([]chronograf.Dashboard, error) { - files, err := d.ReadDir(d.Dir) - if err != nil { - return nil, err - } - - dashboards := []chronograf.Dashboard{} - for _, file := range files { - if path.Ext(file.Name()) != DashExt { - continue - } - var dashboard chronograf.Dashboard - if err := d.Load(path.Join(d.Dir, file.Name()), &dashboard); err != nil { - continue // We want to load all files we can. - } else { - dashboards = append(dashboards, dashboard) - } - } - return dashboards, nil -} - -// Add creates a new dashboard within the directory -func (d *Dashboards) Add(ctx context.Context, dashboard chronograf.Dashboard) (chronograf.Dashboard, error) { - genID, err := d.IDs.Generate() - if err != nil { - d.Logger. - WithField("component", "dashboard"). - Error("Unable to generate ID") - return chronograf.Dashboard{}, err - } - - id, err := strconv.Atoi(genID) - if err != nil { - d.Logger. - WithField("component", "dashboard"). - Error("Unable to convert ID") - return chronograf.Dashboard{}, err - } - - dashboard.ID = chronograf.DashboardID(id) - - file := dashboardFile(d.Dir, dashboard) - if err = d.Create(file, dashboard); err != nil { - if err == chronograf.ErrDashboardInvalid { - d.Logger. - WithField("component", "dashboard"). - WithField("name", file). - Error("Invalid Dashboard: ", err) - } else { - d.Logger. - WithField("component", "dashboard"). - WithField("name", file). - Error("Unable to write dashboard:", err) - } - return chronograf.Dashboard{}, err - } - return dashboard, nil -} - -// Delete removes a dashboard file from the directory -func (d *Dashboards) Delete(ctx context.Context, dashboard chronograf.Dashboard) error { - _, file, err := d.idToFile(dashboard.ID) - if err != nil { - return err - } - - if err := d.Remove(file); err != nil { - d.Logger. - WithField("component", "dashboard"). - WithField("name", file). - Error("Unable to remove dashboard:", err) - return err - } - return nil -} - -// Get returns a dashboard file from the dashboard directory -func (d *Dashboards) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - board, file, err := d.idToFile(id) - if err != nil { - if err == chronograf.ErrDashboardNotFound { - d.Logger. - WithField("component", "dashboard"). - WithField("name", file). - Error("Unable to read file") - } else if err == chronograf.ErrDashboardInvalid { - d.Logger. - WithField("component", "dashboard"). - WithField("name", file). - Error("File is not a dashboard") - } - return chronograf.Dashboard{}, err - } - return board, nil -} - -// Update replaces a dashboard from the file system directory -func (d *Dashboards) Update(ctx context.Context, dashboard chronograf.Dashboard) error { - board, _, err := d.idToFile(dashboard.ID) - if err != nil { - return err - } - - if err := d.Delete(ctx, board); err != nil { - return err - } - file := dashboardFile(d.Dir, dashboard) - return d.Create(file, dashboard) -} - -// idToFile takes an id and finds the associated filename -func (d *Dashboards) idToFile(id chronograf.DashboardID) (chronograf.Dashboard, string, error) { - // Because the entire dashboard information is not known at this point, we need - // to try to find the name of the file through matching the ID in the dashboard - // content with the ID passed. - files, err := d.ReadDir(d.Dir) - if err != nil { - return chronograf.Dashboard{}, "", err - } - - for _, f := range files { - if path.Ext(f.Name()) != DashExt { - continue - } - file := path.Join(d.Dir, f.Name()) - var dashboard chronograf.Dashboard - if err := d.Load(file, &dashboard); err != nil { - return chronograf.Dashboard{}, "", err - } - if dashboard.ID == id { - return dashboard, file, nil - } - } - - return chronograf.Dashboard{}, "", chronograf.ErrDashboardNotFound -} diff --git a/chronograf/filestore/environ.go b/chronograf/filestore/environ.go deleted file mode 100644 index 091e179e802..00000000000 --- a/chronograf/filestore/environ.go +++ /dev/null @@ -1,24 +0,0 @@ -package filestore - -import ( - "os" - "strings" -) - -var env map[string]string - -// environ returns a map of all environment variables in the running process -func environ() map[string]string { - if env == nil { - env = make(map[string]string) - envVars := os.Environ() - for _, envVar := range envVars { - kv := strings.SplitN(envVar, "=", 2) - if len(kv) != 2 { - continue - } - env[kv[0]] = kv[1] - } - } - return env -} diff --git a/chronograf/filestore/environ_test.go b/chronograf/filestore/environ_test.go deleted file mode 100644 index 6894848062b..00000000000 --- a/chronograf/filestore/environ_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package filestore - -import ( - "os" - "testing" -) - -func Test_environ(t *testing.T) { - tests := []struct { - name string - key string - value string - }{ - { - name: "environment variable is returned", - key: "CHRONOGRAF_TEST_ENVIRON", - value: "howdy", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - os.Setenv(tt.key, tt.value) - got := environ() - if v, ok := got[tt.key]; !ok || v != tt.value { - t.Errorf("environ() = %v, want %v", v, tt.value) - } - }) - } -} diff --git a/chronograf/filestore/kapacitors.go b/chronograf/filestore/kapacitors.go deleted file mode 100644 index 6b77c82ef0d..00000000000 --- a/chronograf/filestore/kapacitors.go +++ /dev/null @@ -1,186 +0,0 @@ -package filestore - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path" - "strconv" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// KapExt is the the file extension searched for in the directory for kapacitor files -const KapExt = ".kap" - -var _ chronograf.ServersStore = &Kapacitors{} - -// Kapacitors are JSON kapacitors stored in the filesystem -type Kapacitors struct { - Dir string // Dir is the directory containing the kapacitors. - Load func(string, interface{}) error // Load loads string name and dashboard passed in as interface - Create func(string, interface{}) error // Create will write kapacitor to file. - ReadDir func(dirname string) ([]os.FileInfo, error) // ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename. - Remove func(name string) error // Remove file - IDs chronograf.ID // IDs generate unique ids for new kapacitors - Logger chronograf.Logger -} - -// NewKapacitors constructs a kapacitor store wrapping a file system directory -func NewKapacitors(dir string, ids chronograf.ID, logger chronograf.Logger) chronograf.ServersStore { - return &Kapacitors{ - Dir: dir, - Load: load, - Create: create, - ReadDir: ioutil.ReadDir, - Remove: os.Remove, - IDs: ids, - Logger: logger, - } -} - -func kapacitorFile(dir string, kapacitor chronograf.Server) string { - base := fmt.Sprintf("%s%s", kapacitor.Name, KapExt) - return path.Join(dir, base) -} - -// All returns all kapacitors from the directory -func (d *Kapacitors) All(ctx context.Context) ([]chronograf.Server, error) { - files, err := d.ReadDir(d.Dir) - if err != nil { - return nil, err - } - - kapacitors := []chronograf.Server{} - for _, file := range files { - if path.Ext(file.Name()) != KapExt { - continue - } - var kapacitor chronograf.Server - if err := d.Load(path.Join(d.Dir, file.Name()), &kapacitor); err != nil { - var fmtErr = fmt.Errorf("error loading kapacitor configuration from %v:\n%v", path.Join(d.Dir, file.Name()), err) - d.Logger.Error(fmtErr) - continue // We want to load all files we can. - } else { - kapacitors = append(kapacitors, kapacitor) - } - } - return kapacitors, nil -} - -// Add creates a new kapacitor within the directory -func (d *Kapacitors) Add(ctx context.Context, kapacitor chronograf.Server) (chronograf.Server, error) { - genID, err := d.IDs.Generate() - if err != nil { - d.Logger. - WithField("component", "kapacitor"). - Error("Unable to generate ID") - return chronograf.Server{}, err - } - - id, err := strconv.Atoi(genID) - if err != nil { - d.Logger. - WithField("component", "kapacitor"). - Error("Unable to convert ID") - return chronograf.Server{}, err - } - - kapacitor.ID = id - - file := kapacitorFile(d.Dir, kapacitor) - if err = d.Create(file, kapacitor); err != nil { - if err == chronograf.ErrServerInvalid { - d.Logger. - WithField("component", "kapacitor"). - WithField("name", file). - Error("Invalid Server: ", err) - } else { - d.Logger. - WithField("component", "kapacitor"). - WithField("name", file). - Error("Unable to write kapacitor:", err) - } - return chronograf.Server{}, err - } - return kapacitor, nil -} - -// Delete removes a kapacitor file from the directory -func (d *Kapacitors) Delete(ctx context.Context, kapacitor chronograf.Server) error { - _, file, err := d.idToFile(kapacitor.ID) - if err != nil { - return err - } - - if err := d.Remove(file); err != nil { - d.Logger. - WithField("component", "kapacitor"). - WithField("name", file). - Error("Unable to remove kapacitor:", err) - return err - } - return nil -} - -// Get returns a kapacitor file from the kapacitor directory -func (d *Kapacitors) Get(ctx context.Context, id int) (chronograf.Server, error) { - board, file, err := d.idToFile(id) - if err != nil { - if err == chronograf.ErrServerNotFound { - d.Logger. - WithField("component", "kapacitor"). - WithField("name", file). - Error("Unable to read file") - } else if err == chronograf.ErrServerInvalid { - d.Logger. - WithField("component", "kapacitor"). - WithField("name", file). - Error("File is not a kapacitor") - } - return chronograf.Server{}, err - } - return board, nil -} - -// Update replaces a kapacitor from the file system directory -func (d *Kapacitors) Update(ctx context.Context, kapacitor chronograf.Server) error { - board, _, err := d.idToFile(kapacitor.ID) - if err != nil { - return err - } - - if err := d.Delete(ctx, board); err != nil { - return err - } - file := kapacitorFile(d.Dir, kapacitor) - return d.Create(file, kapacitor) -} - -// idToFile takes an id and finds the associated filename -func (d *Kapacitors) idToFile(id int) (chronograf.Server, string, error) { - // Because the entire kapacitor information is not known at this point, we need - // to try to find the name of the file through matching the ID in the kapacitor - // content with the ID passed. - files, err := d.ReadDir(d.Dir) - if err != nil { - return chronograf.Server{}, "", err - } - - for _, f := range files { - if path.Ext(f.Name()) != KapExt { - continue - } - file := path.Join(d.Dir, f.Name()) - var kapacitor chronograf.Server - if err := d.Load(file, &kapacitor); err != nil { - return chronograf.Server{}, "", err - } - if kapacitor.ID == id { - return kapacitor, file, nil - } - } - - return chronograf.Server{}, "", chronograf.ErrServerNotFound -} diff --git a/chronograf/filestore/organizations.go b/chronograf/filestore/organizations.go deleted file mode 100644 index 41aaadd1b92..00000000000 --- a/chronograf/filestore/organizations.go +++ /dev/null @@ -1,117 +0,0 @@ -package filestore - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// OrgExt is the the file extension searched for in the directory for org files -const OrgExt = ".org" - -var _ chronograf.OrganizationsStore = (*Organizations)(nil) - -// Organizations are JSON orgs stored in the filesystem -type Organizations struct { - Dir string // Dir is the directory containing the orgs. - Load func(string, interface{}) error // Load loads string name and org passed in as interface - ReadDir func(dirname string) ([]os.FileInfo, error) // ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename. - Logger chronograf.Logger -} - -// NewOrganizations constructs a org store wrapping a file system directory -func NewOrganizations(dir string, logger chronograf.Logger) chronograf.OrganizationsStore { - return &Organizations{ - Dir: dir, - Load: load, - ReadDir: ioutil.ReadDir, - Logger: logger, - } -} - -// All returns all orgs from the directory -func (o *Organizations) All(ctx context.Context) ([]chronograf.Organization, error) { - files, err := o.ReadDir(o.Dir) - if err != nil { - return nil, err - } - - orgs := []chronograf.Organization{} - for _, file := range files { - if path.Ext(file.Name()) != OrgExt { - continue - } - var org chronograf.Organization - if err := o.Load(path.Join(o.Dir, file.Name()), &org); err != nil { - continue // We want to load all files we can. - } else { - orgs = append(orgs, org) - } - } - return orgs, nil -} - -// Get returns a org file from the org directory -func (o *Organizations) Get(ctx context.Context, query chronograf.OrganizationQuery) (*chronograf.Organization, error) { - org, _, err := o.findOrg(query) - return org, err -} - -// Add is not allowed for the filesystem organization store -func (o *Organizations) Add(ctx context.Context, org *chronograf.Organization) (*chronograf.Organization, error) { - return nil, fmt.Errorf("unable to add organizations to the filesystem") -} - -// Delete is not allowed for the filesystem organization store -func (o *Organizations) Delete(ctx context.Context, org *chronograf.Organization) error { - return fmt.Errorf("unable to delete an organization from the filesystem") -} - -// Update is not allowed for the filesystem organization store -func (o *Organizations) Update(ctx context.Context, org *chronograf.Organization) error { - return fmt.Errorf("unable to update organizations on the filesystem") -} - -// CreateDefault is not allowed for the filesystem organization store -func (o *Organizations) CreateDefault(ctx context.Context) error { - return fmt.Errorf("unable to create default organizations on the filesystem") -} - -// DefaultOrganization is not allowed for the filesystem organization store -func (o *Organizations) DefaultOrganization(ctx context.Context) (*chronograf.Organization, error) { - return nil, fmt.Errorf("unable to get default organizations from the filestore") -} - -// findOrg takes an OrganizationQuery and finds the associated filename -func (o *Organizations) findOrg(query chronograf.OrganizationQuery) (*chronograf.Organization, string, error) { - // Because the entire org information is not known at this point, we need - // to try to find the name of the file through matching the ID or name in the org - // content with the ID passed. - files, err := o.ReadDir(o.Dir) - if err != nil { - return nil, "", err - } - - for _, f := range files { - if path.Ext(f.Name()) != OrgExt { - continue - } - file := path.Join(o.Dir, f.Name()) - var org chronograf.Organization - if err := o.Load(file, &org); err != nil { - return nil, "", err - } - if query.ID != nil && org.ID == *query.ID { - return &org, file, nil - } - if query.Name != nil && org.Name == *query.Name { - return &org, file, nil - } - } - - return nil, "", chronograf.ErrOrganizationNotFound -} diff --git a/chronograf/filestore/sources.go b/chronograf/filestore/sources.go deleted file mode 100644 index 8c970e673ad..00000000000 --- a/chronograf/filestore/sources.go +++ /dev/null @@ -1,186 +0,0 @@ -package filestore - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path" - "strconv" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// SrcExt is the the file extension searched for in the directory for source files -const SrcExt = ".src" - -var _ chronograf.SourcesStore = &Sources{} - -// Sources are JSON sources stored in the filesystem -type Sources struct { - Dir string // Dir is the directory containing the sources. - Load func(string, interface{}) error // Load loads string name and dashboard passed in as interface - Create func(string, interface{}) error // Create will write source to file. - ReadDir func(dirname string) ([]os.FileInfo, error) // ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename. - Remove func(name string) error // Remove file - IDs chronograf.ID // IDs generate unique ids for new sources - Logger chronograf.Logger -} - -// NewSources constructs a source store wrapping a file system directory -func NewSources(dir string, ids chronograf.ID, logger chronograf.Logger) chronograf.SourcesStore { - return &Sources{ - Dir: dir, - Load: load, - Create: create, - ReadDir: ioutil.ReadDir, - Remove: os.Remove, - IDs: ids, - Logger: logger, - } -} - -func sourceFile(dir string, source chronograf.Source) string { - base := fmt.Sprintf("%s%s", source.Name, SrcExt) - return path.Join(dir, base) -} - -// All returns all sources from the directory -func (d *Sources) All(ctx context.Context) ([]chronograf.Source, error) { - files, err := d.ReadDir(d.Dir) - if err != nil { - return nil, err - } - - sources := []chronograf.Source{} - for _, file := range files { - if path.Ext(file.Name()) != SrcExt { - continue - } - var source chronograf.Source - if err := d.Load(path.Join(d.Dir, file.Name()), &source); err != nil { - var fmtErr = fmt.Errorf("error loading source configuration from %v:\n%v", path.Join(d.Dir, file.Name()), err) - d.Logger.Error(fmtErr) - continue // We want to load all files we can. - } else { - sources = append(sources, source) - } - } - return sources, nil -} - -// Add creates a new source within the directory -func (d *Sources) Add(ctx context.Context, source chronograf.Source) (chronograf.Source, error) { - genID, err := d.IDs.Generate() - if err != nil { - d.Logger. - WithField("component", "source"). - Error("Unable to generate ID") - return chronograf.Source{}, err - } - - id, err := strconv.Atoi(genID) - if err != nil { - d.Logger. - WithField("component", "source"). - Error("Unable to convert ID") - return chronograf.Source{}, err - } - - source.ID = id - - file := sourceFile(d.Dir, source) - if err = d.Create(file, source); err != nil { - if err == chronograf.ErrSourceInvalid { - d.Logger. - WithField("component", "source"). - WithField("name", file). - Error("Invalid Source: ", err) - } else { - d.Logger. - WithField("component", "source"). - WithField("name", file). - Error("Unable to write source:", err) - } - return chronograf.Source{}, err - } - return source, nil -} - -// Delete removes a source file from the directory -func (d *Sources) Delete(ctx context.Context, source chronograf.Source) error { - _, file, err := d.idToFile(source.ID) - if err != nil { - return err - } - - if err := d.Remove(file); err != nil { - d.Logger. - WithField("component", "source"). - WithField("name", file). - Error("Unable to remove source:", err) - return err - } - return nil -} - -// Get returns a source file from the source directory -func (d *Sources) Get(ctx context.Context, id int) (chronograf.Source, error) { - board, file, err := d.idToFile(id) - if err != nil { - if err == chronograf.ErrSourceNotFound { - d.Logger. - WithField("component", "source"). - WithField("name", file). - Error("Unable to read file") - } else if err == chronograf.ErrSourceInvalid { - d.Logger. - WithField("component", "source"). - WithField("name", file). - Error("File is not a source") - } - return chronograf.Source{}, err - } - return board, nil -} - -// Update replaces a source from the file system directory -func (d *Sources) Update(ctx context.Context, source chronograf.Source) error { - board, _, err := d.idToFile(source.ID) - if err != nil { - return err - } - - if err := d.Delete(ctx, board); err != nil { - return err - } - file := sourceFile(d.Dir, source) - return d.Create(file, source) -} - -// idToFile takes an id and finds the associated filename -func (d *Sources) idToFile(id int) (chronograf.Source, string, error) { - // Because the entire source information is not known at this point, we need - // to try to find the name of the file through matching the ID in the source - // content with the ID passed. - files, err := d.ReadDir(d.Dir) - if err != nil { - return chronograf.Source{}, "", err - } - - for _, f := range files { - if path.Ext(f.Name()) != SrcExt { - continue - } - file := path.Join(d.Dir, f.Name()) - var source chronograf.Source - if err := d.Load(file, &source); err != nil { - return chronograf.Source{}, "", err - } - if source.ID == id { - return source, file, nil - } - } - - return chronograf.Source{}, "", chronograf.ErrSourceNotFound -} diff --git a/chronograf/filestore/templates.go b/chronograf/filestore/templates.go deleted file mode 100644 index fc0e1ffc464..00000000000 --- a/chronograf/filestore/templates.go +++ /dev/null @@ -1,28 +0,0 @@ -package filestore - -import ( - "bytes" - "html/template" -) - -// templated returns all files templated using data -func templated(data interface{}, filenames ...string) ([]byte, error) { - t, err := template.ParseFiles(filenames...) - if err != nil { - return nil, err - } - var b bytes.Buffer - // If a key in the file exists but is not in the data we - // immediately fail with a missing key error - err = t.Option("missingkey=error").Execute(&b, data) - if err != nil { - return nil, err - } - - return b.Bytes(), nil -} - -// templatedFromEnv returns all files templated against environment variables -func templatedFromEnv(filenames ...string) ([]byte, error) { - return templated(environ(), filenames...) -} diff --git a/chronograf/filestore/templates_test.go b/chronograf/filestore/templates_test.go deleted file mode 100644 index 5d5b82f5df3..00000000000 --- a/chronograf/filestore/templates_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package filestore - -import ( - "io/ioutil" - "os" - "reflect" - "testing" -) - -func Test_templated(t *testing.T) { - tests := []struct { - name string - content []string - data interface{} - want []byte - wantErr bool - }{ - { - name: "files with templates are rendered correctly", - content: []string{ - "{{ .MYVAR }}", - }, - data: map[string]string{ - "MYVAR": "howdy", - }, - want: []byte("howdy"), - }, - { - name: "missing key gives an error", - content: []string{ - "{{ .MYVAR }}", - }, - wantErr: true, - }, - { - name: "no files make me an error!", - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - filenames := make([]string, len(tt.content)) - for i, c := range tt.content { - f, err := ioutil.TempFile("", "") - if err != nil { - t.Fatal(err) - } - if _, err := f.Write([]byte(c)); err != nil { - t.Fatal(err) - } - filenames[i] = f.Name() - defer os.Remove(f.Name()) - } - got, err := templated(tt.data, filenames...) - if (err != nil) != tt.wantErr { - t.Errorf("templated() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("templated() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/chronograf/integrations/server_test.go b/chronograf/integrations/server_test.go deleted file mode 100644 index 0f6462cb746..00000000000 --- a/chronograf/integrations/server_test.go +++ /dev/null @@ -1,3730 +0,0 @@ -package integrations - -// This was intentionally added under the integrations package and not the integrations test package -// so that changes in other parts of the code base that may have an effect on these test will not -// compile until they are fixed. - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - - "net/http" - "testing" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" - "github.com/influxdata/influxdb/v2/chronograf/server" -) - -func TestServer(t *testing.T) { - type fields struct { - Organizations []chronograf.Organization - Mappings []chronograf.Mapping - Users []chronograf.User - Sources []chronograf.Source - Servers []chronograf.Server - Layouts []chronograf.Layout - Dashboards []chronograf.Dashboard - Config *chronograf.Config - } - type args struct { - server *server.Server - method string - path string - payload interface{} // Expects this to be a json serializable struct - principal oauth2.Principal - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - subName string - fields fields - args args - wants wants - }{ - // { - // name: "GET /sources/5000", - // subName: "Get specific source; including Canned source", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // { - // Name: "viewer", - // Organization: "howdy", // from canned testdata - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/sources/5000", - // principal: oauth2.Principal{ - // Organization: "howdy", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "id": "5000", - // "name": "Influx 1", - // "type": "influx-enterprise", - // "username": "user1", - // "url": "http://localhost:8086", - // "metaUrl": "http://metaurl.com", - // "default": true, - // "telegraf": "telegraf", - // "organization": "howdy", - // "defaultRP": "", - // "authentication": "basic", - // "links": { - // "self": "/chronograf/v1/sources/5000", - // "kapacitors": "/chronograf/v1/sources/5000/kapacitors", - // "services": "/chronograf/v1/sources/5000/services", - // "proxy": "/chronograf/v1/sources/5000/proxy", - // "queries": "/chronograf/v1/sources/5000/queries", - // "write": "/chronograf/v1/sources/5000/write", - // "permissions": "/chronograf/v1/sources/5000/permissions", - // "users": "/chronograf/v1/sources/5000/users", - // "roles": "/chronograf/v1/sources/5000/roles", - // "databases": "/chronograf/v1/sources/5000/dbs", - // "annotations": "/chronograf/v1/sources/5000/annotations", - // "health": "/chronograf/v1/sources/5000/health" - // } - //} - //`, - // }, - // }, - // { - // name: "GET /sources/5000/kapacitors/5000", - // subName: "Get specific kapacitors; including Canned kapacitors", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // { - // Name: "viewer", - // Organization: "howdy", // from canned testdata - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/sources/5000/kapacitors/5000", - // principal: oauth2.Principal{ - // Organization: "howdy", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "id": "5000", - // "name": "Kapa 1", - // "url": "http://localhost:9092", - // "active": true, - // "insecureSkipVerify": false, - // "links": { - // "proxy": "/chronograf/v1/sources/5000/kapacitors/5000/proxy", - // "self": "/chronograf/v1/sources/5000/kapacitors/5000", - // "rules": "/chronograf/v1/sources/5000/kapacitors/5000/rules", - // "tasks": "/chronograf/v1/sources/5000/kapacitors/5000/proxy?path=/kapacitor/v1/tasks", - // "ping": "/chronograf/v1/sources/5000/kapacitors/5000/proxy?path=/kapacitor/v1/ping" - // } - //} - //`, - // }, - // }, - // { - // name: "GET /sources/5000/kapacitors", - // subName: "Get all kapacitors; including Canned kapacitors", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // { - // Name: "viewer", - // Organization: "howdy", // from canned testdata - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/sources/5000/kapacitors", - // principal: oauth2.Principal{ - // Organization: "howdy", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "kapacitors": [ - // { - // "id": "5000", - // "name": "Kapa 1", - // "url": "http://localhost:9092", - // "active": true, - // "insecureSkipVerify": false, - // "links": { - // "proxy": "/chronograf/v1/sources/5000/kapacitors/5000/proxy", - // "self": "/chronograf/v1/sources/5000/kapacitors/5000", - // "rules": "/chronograf/v1/sources/5000/kapacitors/5000/rules", - // "tasks": "/chronograf/v1/sources/5000/kapacitors/5000/proxy?path=/kapacitor/v1/tasks", - // "ping": "/chronograf/v1/sources/5000/kapacitors/5000/proxy?path=/kapacitor/v1/ping" - // } - // } - // ] - //} - //`, - // }, - // }, - // { - // name: "GET /sources", - // subName: "Get all sources; including Canned sources", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // { - // Name: "viewer", - // Organization: "howdy", // from canned testdata - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/sources", - // principal: oauth2.Principal{ - // Organization: "howdy", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "sources": [ - // { - // "id": "5000", - // "name": "Influx 1", - // "type": "influx-enterprise", - // "username": "user1", - // "url": "http://localhost:8086", - // "metaUrl": "http://metaurl.com", - // "default": true, - // "telegraf": "telegraf", - // "organization": "howdy", - // "defaultRP": "", - // "authentication": "basic", - // "links": { - // "self": "/chronograf/v1/sources/5000", - // "kapacitors": "/chronograf/v1/sources/5000/kapacitors", - // "services": "/chronograf/v1/sources/5000/services", - // "proxy": "/chronograf/v1/sources/5000/proxy", - // "queries": "/chronograf/v1/sources/5000/queries", - // "write": "/chronograf/v1/sources/5000/write", - // "permissions": "/chronograf/v1/sources/5000/permissions", - // "users": "/chronograf/v1/sources/5000/users", - // "roles": "/chronograf/v1/sources/5000/roles", - // "databases": "/chronograf/v1/sources/5000/dbs", - // "annotations": "/chronograf/v1/sources/5000/annotations", - // "health": "/chronograf/v1/sources/5000/health" - // } - // } - // ] - //} - //`, - // }, - // }, - // { - // name: "GET /organizations", - // subName: "Get all organizations; including Canned organization", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/organizations", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/organizations" - // }, - // "organizations": [ - // { - // "links": { - // "self": "/chronograf/v1/organizations/default" - // }, - // "id": "default", - // "name": "Default", - // "defaultRole": "member" - // }, - // { - // "links": { - // "self": "/chronograf/v1/organizations/howdy" - // }, - // "id": "howdy", - // "name": "An Organization", - // "defaultRole": "viewer" - // } - // ] - //}`, - // }, - // }, - // { - // name: "GET /organizations/howdy", - // subName: "Get specific organizations; Canned organization", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/organizations/howdy", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/organizations/howdy" - // }, - // "id": "howdy", - // "name": "An Organization", - // "defaultRole": "viewer" - //}`, - // }, - // }, - // { - // name: "GET /dashboards/1000", - // subName: "Get specific in the howdy organization; Using Canned testdata", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "howdy", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/dashboards/1000", - // principal: oauth2.Principal{ - // Organization: "howdy", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "id": 1000, - // "cells": [ - // { - // "i": "8f61c619-dd9b-4761-8aa8-577f27247093", - // "x": 0, - // "y": 0, - // "w": 11, - // "h": 5, - // "name": "Untitled Cell", - // "queries": [ - // { - // "query": "SELECT mean(\"value\") AS \"mean_value\" FROM \"telegraf\".\"autogen\".\"cpg\" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)", - // "queryConfig": { - // "database": "telegraf", - // "measurement": "cpg", - // "retentionPolicy": "autogen", - // "fields": [ - // { - // "value": "mean", - // "type": "func", - // "alias": "mean_value", - // "args": [ - // { - // "value": "value", - // "type": "field", - // "alias": "" - // } - // ] - // } - // ], - // "tags": {}, - // "groupBy": { - // "time": "auto", - // "tags": [] - // }, - // "areTagsAccepted": false, - // "fill": "null", - // "rawText": null, - // "range": null, - // "shifts": null - // }, - // "source": "/chronograf/v1/sources/2" - // } - // ], - // "axes": { - // "x": { - // "bounds": [], - // "label": "", - // "prefix": "", - // "suffix": "", - // "base": "10", - // "scale": "linear" - // }, - // "y": { - // "bounds": [], - // "label": "", - // "prefix": "", - // "suffix": "", - // "base": "10", - // "scale": "linear" - // }, - // "y2": { - // "bounds": [], - // "label": "", - // "prefix": "", - // "suffix": "", - // "base": "10", - // "scale": "linear" - // } - // }, - // "type": "line", - // "colors": [ - // { - // "id": "0", - // "type": "min", - // "hex": "#00C9FF", - // "name": "laser", - // "value": "0" - // }, - // { - // "id": "1", - // "type": "max", - // "hex": "#9394FF", - // "name": "comet", - // "value": "100" - // } - // ], - // "legend":{ - // "type": "static", - // "orientation": "bottom" - // }, - // "tableOptions":{ - // "verticalTimeAxis": false, - // "sortBy":{ - // "internalName": "", - // "displayName": "", - // "visible": false - // }, - // "wrapping": "", - // "fixFirstColumn": false - // }, - // "fieldOptions": null, - // "timeFormat": "", - // "decimalPlaces":{ - // "isEnforced": false, - // "digits": 0 - // }, - // "links": { - // "self": "/chronograf/v1/dashboards/1000/cells/8f61c619-dd9b-4761-8aa8-577f27247093" - // } - // } - // ], - // "templates": [ - // { - // "tempVar": ":dbs:", - // "values": [ - // { - // "value": "_internal", - // "type": "database", - // "selected": true - // }, - // { - // "value": "telegraf", - // "type": "database", - // "selected": false - // }, - // { - // "value": "tensorflowdb", - // "type": "database", - // "selected": false - // }, - // { - // "value": "pushgateway", - // "type": "database", - // "selected": false - // }, - // { - // "value": "node_exporter", - // "type": "database", - // "selected": false - // }, - // { - // "value": "mydb", - // "type": "database", - // "selected": false - // }, - // { - // "value": "tiny", - // "type": "database", - // "selected": false - // }, - // { - // "value": "blah", - // "type": "database", - // "selected": false - // }, - // { - // "value": "test", - // "type": "database", - // "selected": false - // }, - // { - // "value": "chronograf", - // "type": "database", - // "selected": false - // }, - // { - // "value": "db_name", - // "type": "database", - // "selected": false - // }, - // { - // "value": "demo", - // "type": "database", - // "selected": false - // }, - // { - // "value": "eeg", - // "type": "database", - // "selected": false - // }, - // { - // "value": "solaredge", - // "type": "database", - // "selected": false - // }, - // { - // "value": "zipkin", - // "type": "database", - // "selected": false - // } - // ], - // "id": "e7e498bf-5869-4874-9071-24628a2cda63", - // "type": "databases", - // "label": "", - // "query": { - // "influxql": "SHOW DATABASES", - // "measurement": "", - // "tagKey": "", - // "fieldKey": "" - // }, - // "links": { - // "self": "/chronograf/v1/dashboards/1000/templates/e7e498bf-5869-4874-9071-24628a2cda63" - // } - // } - // ], - // "name": "Name This Dashboard", - // "organization": "howdy", - // "links": { - // "self": "/chronograf/v1/dashboards/1000", - // "cells": "/chronograf/v1/dashboards/1000/cells", - // "templates": "/chronograf/v1/dashboards/1000/templates" - // } - //}`, - // }, - // }, - // { - // name: "GET /dashboards", - // subName: "Get all dashboards in the howdy organization; Using Canned testdata", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // { - // Name: "admin", - // Organization: "howdy", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/dashboards", - // principal: oauth2.Principal{ - // Organization: "howdy", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "dashboards": [ - // { - // "id": 1000, - // "cells": [ - // { - // "i": "8f61c619-dd9b-4761-8aa8-577f27247093", - // "x": 0, - // "y": 0, - // "w": 11, - // "h": 5, - // "name": "Untitled Cell", - // "queries": [ - // { - // "query": "SELECT mean(\"value\") AS \"mean_value\" FROM \"telegraf\".\"autogen\".\"cpg\" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)", - // "queryConfig": { - // "database": "telegraf", - // "measurement": "cpg", - // "retentionPolicy": "autogen", - // "fields": [ - // { - // "value": "mean", - // "type": "func", - // "alias": "mean_value", - // "args": [ - // { - // "value": "value", - // "type": "field", - // "alias": "" - // } - // ] - // } - // ], - // "tags": {}, - // "groupBy": { - // "time": "auto", - // "tags": [] - // }, - // "areTagsAccepted": false, - // "fill": "null", - // "rawText": null, - // "range": null, - // "shifts": null - // }, - // "source": "/chronograf/v1/sources/2" - // } - // ], - // "axes": { - // "x": { - // "bounds": [], - // "label": "", - // "prefix": "", - // "suffix": "", - // "base": "10", - // "scale": "linear" - // }, - // "y": { - // "bounds": [], - // "label": "", - // "prefix": "", - // "suffix": "", - // "base": "10", - // "scale": "linear" - // }, - // "y2": { - // "bounds": [], - // "label": "", - // "prefix": "", - // "suffix": "", - // "base": "10", - // "scale": "linear" - // } - // }, - // "type": "line", - // "colors": [ - // { - // "id": "0", - // "type": "min", - // "hex": "#00C9FF", - // "name": "laser", - // "value": "0" - // }, - // { - // "id": "1", - // "type": "max", - // "hex": "#9394FF", - // "name": "comet", - // "value": "100" - // } - // ], - // "legend": { - // "type": "static", - // "orientation": "bottom" - // }, - // "tableOptions":{ - // "verticalTimeAxis": false, - // "sortBy":{ - // "internalName": "", - // "displayName": "", - // "visible": false - // }, - // "wrapping": "", - // "fixFirstColumn": false - // }, - // "fieldOptions": null, - // "timeFormat": "", - // "decimalPlaces":{ - // "isEnforced": false, - // "digits": 0 - // }, - // "links": { - // "self": "/chronograf/v1/dashboards/1000/cells/8f61c619-dd9b-4761-8aa8-577f27247093" - // } - // } - // ], - // "templates": [ - // { - // "tempVar": ":dbs:", - // "values": [ - // { - // "value": "_internal", - // "type": "database", - // "selected": true - // }, - // { - // "value": "telegraf", - // "type": "database", - // "selected": false - // }, - // { - // "value": "tensorflowdb", - // "type": "database", - // "selected": false - // }, - // { - // "value": "pushgateway", - // "type": "database", - // "selected": false - // }, - // { - // "value": "node_exporter", - // "type": "database", - // "selected": false - // }, - // { - // "value": "mydb", - // "type": "database", - // "selected": false - // }, - // { - // "value": "tiny", - // "type": "database", - // "selected": false - // }, - // { - // "value": "blah", - // "type": "database", - // "selected": false - // }, - // { - // "value": "test", - // "type": "database", - // "selected": false - // }, - // { - // "value": "chronograf", - // "type": "database", - // "selected": false - // }, - // { - // "value": "db_name", - // "type": "database", - // "selected": false - // }, - // { - // "value": "demo", - // "type": "database", - // "selected": false - // }, - // { - // "value": "eeg", - // "type": "database", - // "selected": false - // }, - // { - // "value": "solaredge", - // "type": "database", - // "selected": false - // }, - // { - // "value": "zipkin", - // "type": "database", - // "selected": false - // } - // ], - // "id": "e7e498bf-5869-4874-9071-24628a2cda63", - // "type": "databases", - // "label": "", - // "query": { - // "influxql": "SHOW DATABASES", - // "measurement": "", - // "tagKey": "", - // "fieldKey": "" - // }, - // "links": { - // "self": "/chronograf/v1/dashboards/1000/templates/e7e498bf-5869-4874-9071-24628a2cda63" - // } - // } - // ], - // "name": "Name This Dashboard", - // "organization": "howdy", - // "links": { - // "self": "/chronograf/v1/dashboards/1000", - // "cells": "/chronograf/v1/dashboards/1000/cells", - // "templates": "/chronograf/v1/dashboards/1000/templates" - // } - // } - // ] - //}`, - // }, - // }, - // { - // name: "GET /users", - // subName: "User Not Found in the Default Organization", - // fields: fields{ - // Users: []chronograf.User{}, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/organizations/default/users", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 403, - // body: `{"code":403,"message":"User is not authorized"}`, - // }, - // }, - // { - // name: "GET /users", - // subName: "Single User in the Default Organization as SuperAdmin", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/organizations/default/users", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/organizations/default/users" - // }, - // "users": [ - // { - // "links": { - // "self": "/chronograf/v1/organizations/default/users/1" - // }, - // "id": "1", - // "name": "billibob", - // "provider": "github", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": [ - // { - // "name": "admin", - // "organization": "default" - // } - // ] - // } - // ] - //}`, - // }, - // }, - // { - // name: "GET /users", - // subName: "Two users in two organizations; user making request is as SuperAdmin with out raw query param", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // { - // ID: 2, // This is artificial, but should be reflective of the users actual ID - // Name: "billietta", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "cool", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/organizations/default/users", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/organizations/default/users" - // }, - // "users": [ - // { - // "links": { - // "self": "/chronograf/v1/organizations/default/users/1" - // }, - // "id": "1", - // "name": "billibob", - // "provider": "github", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": [ - // { - // "name": "admin", - // "organization": "default" - // } - // ] - // } - // ] - //} - //`, - // }, - // }, - // { - // name: "POST /users", - // subName: "User making request is as SuperAdmin with raw query param; being created has wildcard role", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // payload: &chronograf.User{ - // Name: "user", - // Provider: "provider", - // Scheme: "oauth2", - // Roles: []chronograf.Role{ - // { - // Name: "*", - // Organization: "default", - // }, - // }, - // }, - // method: "POST", - // path: "/chronograf/v1/users", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 201, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/users/2" - // }, - // "id": "2", - // "name": "user", - // "provider": "provider", - // "scheme": "oauth2", - // "superAdmin": false, - // "roles": [ - // { - // "name": "member", - // "organization": "default" - // } - // ] - //} - //`, - // }, - // }, - // { - // name: "POST /users", - // subName: "User making request is as SuperAdmin with raw query param; being created has no roles", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // payload: &chronograf.User{ - // Name: "user", - // Provider: "provider", - // Scheme: "oauth2", - // Roles: []chronograf.Role{}, - // }, - // method: "POST", - // path: "/chronograf/v1/users", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 201, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/users/2" - // }, - // "id": "2", - // "name": "user", - // "provider": "provider", - // "scheme": "oauth2", - // "superAdmin": false, - // "roles": [] - //} - //`, - // }, - // }, - // { - // name: "GET /users", - // subName: "Two users in two organizations; user making request is as SuperAdmin with raw query param", - // fields: fields{ - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "cool", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // { - // ID: 2, // This is artificial, but should be reflective of the users actual ID - // Name: "billietta", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "1", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/users", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/users" - // }, - // "users": [ - // { - // "links": { - // "self": "/chronograf/v1/users/1" - // }, - // "id": "1", - // "name": "billibob", - // "provider": "github", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": [ - // { - // "name": "admin", - // "organization": "default" - // } - // ] - // }, - // { - // "links": { - // "self": "/chronograf/v1/users/2" - // }, - // "id": "2", - // "name": "billietta", - // "provider": "github", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": [ - // { - // "name": "admin", - // "organization": "1" - // } - // ] - // } - // ] - //} - //`, - // }, - // }, - // { - // name: "GET /users", - // subName: "Two users in two organizations; user making request is as not SuperAdmin with raw query param", - // fields: fields{ - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "cool", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // { - // ID: 2, // This is artificial, but should be reflective of the users actual ID - // Name: "billietta", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: false, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // { - // Name: "admin", - // Organization: "1", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/users", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billieta", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 403, - // body: ` - //{ - // "code": 403, - // "message": "User is not authorized" - //} - //`, - // }, - // }, - // { - // name: "POST /users", - // subName: "Create a New User with SuperAdmin status; SuperAdminNewUsers is true (the default case); User on Principal is a SuperAdmin", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: true, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "POST", - // path: "/chronograf/v1/organizations/default/users", - // payload: &chronograf.User{ - // Name: "user", - // Provider: "provider", - // Scheme: "oauth2", - // Roles: []chronograf.Role{ - // { - // Name: roles.EditorRoleName, - // Organization: "default", - // }, - // }, - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 201, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/organizations/default/users/2" - // }, - // "id": "2", - // "name": "user", - // "provider": "provider", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": [ - // { - // "name": "editor", - // "organization": "default" - // } - // ] - //}`, - // }, - // }, - // { - // name: "POST /users", - // subName: "Create a New User with SuperAdmin status; SuperAdminNewUsers is false; User on Principal is a SuperAdmin", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "POST", - // path: "/chronograf/v1/organizations/default/users", - // payload: &chronograf.User{ - // Name: "user", - // Provider: "provider", - // Scheme: "oauth2", - // Roles: []chronograf.Role{ - // { - // Name: roles.EditorRoleName, - // Organization: "default", - // }, - // }, - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 201, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/organizations/default/users/2" - // }, - // "id": "2", - // "name": "user", - // "provider": "provider", - // "scheme": "oauth2", - // "superAdmin": false, - // "roles": [ - // { - // "name": "editor", - // "organization": "default" - // } - // ] - //}`, - // }, - // }, - // { - // name: "POST /users", - // subName: "Create a New User with SuperAdmin status; SuperAdminNewUsers is false; User on Principal is Admin, but not a SuperAdmin", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: false, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "POST", - // path: "/chronograf/v1/organizations/default/users", - // payload: &chronograf.User{ - // Name: "user", - // Provider: "provider", - // Scheme: "oauth2", - // Roles: []chronograf.Role{ - // { - // Name: roles.EditorRoleName, - // Organization: "default", - // }, - // }, - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 201, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/organizations/default/users/2" - // }, - // "id": "2", - // "name": "user", - // "provider": "provider", - // "scheme": "oauth2", - // "superAdmin": false, - // "roles": [ - // { - // "name": "editor", - // "organization": "default" - // } - // ] - //}`, - // }, - // }, - // { - // name: "POST /users", - // subName: "Create a New User with SuperAdmin status; SuperAdminNewUsers is true; User on Principal is Admin, but not a SuperAdmin", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: true, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: false, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "POST", - // path: "/chronograf/v1/organizations/default/users", - // payload: &chronograf.User{ - // Name: "user", - // Provider: "provider", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: roles.EditorRoleName, - // Organization: "default", - // }, - // }, - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 401, - // body: ` - //{ - // "code": 401, - // "message": "user does not have authorization required to set SuperAdmin status. See https://github.com/influxdata/influxdb/chronograf/issues/2601 for more information." - //}`, - // }, - // }, - // { - // name: "POST /users", - // subName: "Create a New User with in multiple organizations; User on Principal is a SuperAdmin with raw query param", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: true, - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "cool", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "POST", - // path: "/chronograf/v1/users", - // payload: &chronograf.User{ - // Name: "user", - // Provider: "provider", - // Scheme: "oauth2", - // Roles: []chronograf.Role{ - // { - // Name: roles.EditorRoleName, - // Organization: "default", - // }, - // { - // Name: roles.EditorRoleName, - // Organization: "1", - // }, - // }, - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 201, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/users/2" - // }, - // "id": "2", - // "name": "user", - // "provider": "provider", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": [ - // { - // "name": "editor", - // "organization": "default" - // }, - // { - // "name": "editor", - // "organization": "1" - // } - // ] - //}`, - // }, - // }, - // { - // name: "PATCH /users", - // subName: "Update user to have no roles", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: true, - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "cool", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "PATCH", - // path: "/chronograf/v1/users/1", - // payload: map[string]interface{}{ - // "name": "billibob", - // "provider": "github", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": []chronograf.Role{}, - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/users/1" - // }, - // "id": "1", - // "name": "billibob", - // "provider": "github", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": [ - // ] - //}`, - // }, - // }, - // { - // name: "PATCH /users", - // subName: "Update user roles with wildcard", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: true, - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "cool", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "PATCH", - // path: "/chronograf/v1/users/1", - // payload: &chronograf.User{ - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: roles.AdminRoleName, - // Organization: "default", - // }, - // { - // Name: roles.WildcardRoleName, - // Organization: "1", - // }, - // }, - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/users/1" - // }, - // "id": "1", - // "name": "billibob", - // "provider": "github", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": [ - // { - // "name": "admin", - // "organization": "default" - // }, - // { - // "name": "viewer", - // "organization": "1" - // } - // ] - //}`, - // }, - // }, - // { - // name: "PATCH /users/1", - // subName: "SuperAdmin modifying their own status", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "PATCH", - // path: "/chronograf/v1/organizations/default/users/1", - // payload: map[string]interface{}{ - // "id": "1", - // "superAdmin": false, - // "roles": []interface{}{ - // map[string]interface{}{ - // "name": "admin", - // "organization": "default", - // }, - // }, - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: http.StatusUnauthorized, - // body: ` - //{ - // "code": 401, - // "message": "user cannot modify their own SuperAdmin status" - //} - //`, - // }, - // }, - // { - // name: "GET /organization/default/users", - // subName: "Organization not set explicitly on principal", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Organizations: []chronograf.Organization{}, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/organizations/default/users", - // principal: oauth2.Principal{ - // Organization: "", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/organizations/default/users" - // }, - // "users": [ - // { - // "links": { - // "self": "/chronograf/v1/organizations/default/users/1" - // }, - // "id": "1", - // "name": "billibob", - // "provider": "github", - // "scheme": "oauth2", - // "superAdmin": true, - // "roles": [ - // { - // "name": "admin", - // "organization": "default" - // } - // ] - // } - // ] - //} - //`, - // }, - // }, - // { - // name: "PUT /me", - // subName: "Change SuperAdmins current organization to org they dont belong to", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "Sweet", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "PUT", - // path: "/chronograf/v1/me", - // payload: map[string]string{ - // "organization": "1", - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "id": "1", - // "name": "billibob", - // "roles": [ - // { - // "name": "admin", - // "organization": "default" - // }, - // { - // "name": "viewer", - // "organization": "1" - // } - // ], - // "provider": "github", - // "scheme": "oauth2", - // "superAdmin": true, - // "links": { - // "self": "/chronograf/v1/organizations/1/users/1" - // }, - // "organizations": [ - // { - // "id": "1", - // "name": "Sweet", - // "defaultRole": "viewer" - // }, - // { - // "id": "default", - // "name": "Default", - // "defaultRole": "member" - // } - // ], - // "currentOrganization": { - // "id": "1", - // "name": "Sweet", - // "defaultRole": "viewer" - // } - //}`, - // }, - // }, - // { - // name: "PUT /me", - // subName: "Change Admin current organization to org they dont belong to", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "Sweet", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: false, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "PUT", - // path: "/chronograf/v1/me", - // payload: map[string]string{ - // "organization": "1", - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 403, - // body: ` - // { - // "code": 403, - // "message": "user not found" - //}`, - // }, - // }, - // { - // name: "GET /me", - // subName: "New user hits me for the first time", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Mappings: []chronograf.Mapping{ - // { - // ID: "1", - // Organization: "1", - // Provider: "*", - // Scheme: "*", - // ProviderOrganization: "influxdata", - // }, - // { - // ID: "1", - // Organization: "1", - // Provider: "*", - // Scheme: "*", - // ProviderOrganization: "*", - // }, - // { - // ID: "2", - // Organization: "2", - // Provider: "github", - // Scheme: "*", - // ProviderOrganization: "*", - // }, - // { - // ID: "3", - // Organization: "3", - // Provider: "auth0", - // Scheme: "ldap", - // ProviderOrganization: "*", - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "Sweet", - // DefaultRole: roles.ViewerRoleName, - // }, - // { - // ID: "2", - // Name: "What", - // DefaultRole: roles.EditorRoleName, - // }, - // { - // ID: "3", - // Name: "Okay", - // DefaultRole: roles.AdminRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{}, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/me", - // principal: oauth2.Principal{ - // Subject: "billietta", - // Issuer: "github", - // Group: "influxdata,idk,mimi", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "id": "2", - // "name": "billietta", - // "roles": [ - // { - // "name": "viewer", - // "organization": "1" - // }, - // { - // "name": "editor", - // "organization": "2" - // }, - // { - // "name": "member", - // "organization": "default" - // } - // ], - // "provider": "github", - // "scheme": "oauth2", - // "links": { - // "self": "/chronograf/v1/organizations/default/users/2" - // }, - // "organizations": [ - // { - // "id": "1", - // "name": "Sweet", - // "defaultRole": "viewer" - // }, - // { - // "id": "2", - // "name": "What", - // "defaultRole": "editor" - // }, - // { - // "id": "default", - // "name": "Default", - // "defaultRole": "member" - // } - // ], - // "currentOrganization": { - // "id": "default", - // "name": "Default", - // "defaultRole": "member" - // } - //} - //`, - // }, - // }, - // { - // name: "GET /mappings", - // subName: "get all mappings", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Mappings: []chronograf.Mapping{ - // { - // ID: "1", - // Organization: "1", - // Provider: "*", - // Scheme: "*", - // ProviderOrganization: "influxdata", - // }, - // { - // ID: "1", - // Organization: "1", - // Provider: "*", - // Scheme: "*", - // ProviderOrganization: "*", - // }, - // { - // ID: "2", - // Organization: "2", - // Provider: "github", - // Scheme: "*", - // ProviderOrganization: "*", - // }, - // { - // ID: "3", - // Organization: "3", - // Provider: "auth0", - // Scheme: "ldap", - // ProviderOrganization: "*", - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "Sweet", - // DefaultRole: roles.ViewerRoleName, - // }, - // { - // ID: "2", - // Name: "What", - // DefaultRole: roles.EditorRoleName, - // }, - // { - // ID: "3", - // Name: "Okay", - // DefaultRole: roles.AdminRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/mappings", - // principal: oauth2.Principal{ - // Subject: "billibob", - // Issuer: "github", - // Group: "influxdata,idk,mimi", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/mappings" - // }, - // "mappings": [ - // { - // "links": { - // "self": "/chronograf/v1/mappings/1" - // }, - // "id": "1", - // "organizationId": "1", - // "provider": "*", - // "scheme": "*", - // "providerOrganization": "influxdata" - // }, - // { - // "links": { - // "self": "/chronograf/v1/mappings/2" - // }, - // "id": "2", - // "organizationId": "1", - // "provider": "*", - // "scheme": "*", - // "providerOrganization": "*" - // }, - // { - // "links": { - // "self": "/chronograf/v1/mappings/3" - // }, - // "id": "3", - // "organizationId": "2", - // "provider": "github", - // "scheme": "*", - // "providerOrganization": "*" - // }, - // { - // "links": { - // "self": "/chronograf/v1/mappings/4" - // }, - // "id": "4", - // "organizationId": "3", - // "provider": "auth0", - // "scheme": "ldap", - // "providerOrganization": "*" - // }, - // { - // "links": { - // "self": "/chronograf/v1/mappings/default" - // }, - // "id": "default", - // "organizationId": "default", - // "provider": "*", - // "scheme": "*", - // "providerOrganization": "*" - // } - // ] - //} - //`, - // }, - // }, - // { - // name: "GET /mappings", - // subName: "get all mappings - user is not super admin", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Mappings: []chronograf.Mapping{ - // { - // ID: "1", - // Organization: "1", - // Provider: "*", - // Scheme: "*", - // ProviderOrganization: "influxdata", - // }, - // { - // ID: "1", - // Organization: "1", - // Provider: "*", - // Scheme: "*", - // ProviderOrganization: "*", - // }, - // { - // ID: "2", - // Organization: "2", - // Provider: "github", - // Scheme: "*", - // ProviderOrganization: "*", - // }, - // { - // ID: "3", - // Organization: "3", - // Provider: "auth0", - // Scheme: "ldap", - // ProviderOrganization: "*", - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "Sweet", - // DefaultRole: roles.ViewerRoleName, - // }, - // { - // ID: "2", - // Name: "What", - // DefaultRole: roles.EditorRoleName, - // }, - // { - // ID: "3", - // Name: "Okay", - // DefaultRole: roles.AdminRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: false, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/mappings", - // principal: oauth2.Principal{ - // Subject: "billibob", - // Issuer: "github", - // Group: "influxdata,idk,mimi", - // }, - // }, - // wants: wants{ - // statusCode: 403, - // body: ` - //{ - // "code": 403, - // "message": "User is not authorized" - //} - //`, - // }, - // }, - // { - // name: "POST /mappings", - // subName: "create new mapping", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Mappings: []chronograf.Mapping{}, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "Sweet", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "POST", - // path: "/chronograf/v1/mappings", - // payload: &chronograf.Mapping{ - // ID: "1", - // Organization: "1", - // Provider: "*", - // Scheme: "*", - // ProviderOrganization: "influxdata", - // }, - // principal: oauth2.Principal{ - // Subject: "billibob", - // Issuer: "github", - // Group: "influxdata,idk,mimi", - // }, - // }, - // wants: wants{ - // statusCode: 201, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/mappings/1" - // }, - // "id": "1", - // "organizationId": "1", - // "provider": "*", - // "scheme": "*", - // "providerOrganization": "influxdata" - //} - //`, - // }, - // }, - // { - // name: "PUT /mappings", - // subName: "update new mapping", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: false, - // }, - // }, - // Mappings: []chronograf.Mapping{ - // chronograf.Mapping{ - // ID: "1", - // Organization: "1", - // Provider: "*", - // Scheme: "*", - // ProviderOrganization: "influxdata", - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "Sweet", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "PUT", - // path: "/chronograf/v1/mappings/1", - // payload: &chronograf.Mapping{ - // ID: "1", - // Organization: "1", - // Provider: "*", - // Scheme: "*", - // ProviderOrganization: "*", - // }, - // principal: oauth2.Principal{ - // Subject: "billibob", - // Issuer: "github", - // Group: "influxdata,idk,mimi", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "links": { - // "self": "/chronograf/v1/mappings/1" - // }, - // "id": "1", - // "organizationId": "1", - // "provider": "*", - // "scheme": "*", - // "providerOrganization": "*" - //} - //`, - // }, - // }, - // { - // name: "GET /org_config", - // subName: "default org", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/org_config", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - // { - // "links": { - // "self": "\/chronograf\/v1\/org_config", - // "logViewer": "\/chronograf\/v1\/org_config\/logviewer" - // }, - // "organization": "default", - // "logViewer": { - // "columns": [ - // { - // "name": "time", - // "position": 0, - // "encodings": [ - // { - // "type": "visibility", - // "value": "hidden" - // } - // ] - // }, - // { - // "name": "severity", - // "position": 1, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // }, - // { - // "type": "label", - // "value": "icon" - // }, - // { - // "type": "label", - // "value": "text" - // }, - // { - // "type": "color", - // "value": "ruby", - // "name": "emerg" - // }, - // { - // "type": "color", - // "value": "fire", - // "name": "alert" - // }, - // { - // "type": "color", - // "value": "curacao", - // "name": "crit" - // }, - // { - // "type": "color", - // "value": "tiger", - // "name": "err" - // }, - // { - // "type": "color", - // "value": "pineapple", - // "name": "warning" - // }, - // { - // "type": "color", - // "value": "rainforest", - // "name": "notice" - // }, - // { - // "type": "color", - // "value": "star", - // "name": "info" - // }, - // { - // "type": "color", - // "value": "wolf", - // "name": "debug" - // } - // ] - // }, - // { - // "name": "timestamp", - // "position": 2, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // }, - // { - // "name": "message", - // "position": 3, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // }, - // { - // "name": "facility", - // "position": 4, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // }, - // { - // "name": "procid", - // "position": 5, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // }, - // { - // "type": "displayName", - // "value": "Proc ID" - // } - // ] - // }, - // { - // "name": "appname", - // "position": 6, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // }, - // { - // "type": "displayName", - // "value": "Application" - // } - // ] - // }, - // { - // "name": "host", - // "position": 7, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // } - // ] - // } - // } - // `, - // }, - // }, - // { - // name: "GET /org_config/logviewer", - // subName: "default org", - // fields: fields{ - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/org_config/logviewer", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - // { - // "links": { - // "self": "\/chronograf\/v1\/org_config/logviewer" - // }, - // "columns": [ - // { - // "name": "time", - // "position": 0, - // "encodings": [ - // { - // "type": "visibility", - // "value": "hidden" - // } - // ] - // }, - // { - // "name": "severity", - // "position": 1, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // }, - // { - // "type": "label", - // "value": "icon" - // }, - // { - // "type": "label", - // "value": "text" - // }, - // { - // "type": "color", - // "value": "ruby", - // "name": "emerg" - // }, - // { - // "type": "color", - // "value": "fire", - // "name": "alert" - // }, - // { - // "type": "color", - // "value": "curacao", - // "name": "crit" - // }, - // { - // "type": "color", - // "value": "tiger", - // "name": "err" - // }, - // { - // "type": "color", - // "value": "pineapple", - // "name": "warning" - // }, - // { - // "type": "color", - // "value": "rainforest", - // "name": "notice" - // }, - // { - // "type": "color", - // "value": "star", - // "name": "info" - // }, - // { - // "type": "color", - // "value": "wolf", - // "name": "debug" - // } - // ] - // }, - // { - // "name": "timestamp", - // "position": 2, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // }, - // { - // "name": "message", - // "position": 3, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // }, - // { - // "name": "facility", - // "position": 4, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // }, - // { - // "name": "procid", - // "position": 5, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // }, - // { - // "type": "displayName", - // "value": "Proc ID" - // } - // ] - // }, - // { - // "name": "appname", - // "position": 6, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // }, - // { - // "type": "displayName", - // "value": "Application" - // } - // ] - // }, - // { - // "name": "host", - // "position": 7, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // } - // ] - // } - // `, - // }, - // }, - // { - // name: "PUT /org_config/logviewer", - // subName: "default org", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: true, - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "cool", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "PUT", - // path: "/chronograf/v1/org_config/logviewer", - // payload: &chronograf.LogViewerConfig{ - // Columns: []chronograf.LogViewerColumn{ - // { - // Name: "time", - // Position: 0, - // Encodings: []chronograf.ColumnEncoding{ - // { - // Type: "visibility", - // Value: "hidden", - // }, - // }, - // }, - // { - // Name: "severity", - // Position: 1, - // Encodings: []chronograf.ColumnEncoding{ - // - // { - // Type: "visibility", - // Value: "visible", - // }, - // { - // Type: "label", - // Value: "icon", - // }, - // { - // Type: "color", - // Name: "emerg", - // Value: "ruby", - // }, - // { - // Type: "color", - // Name: "alert", - // Value: "fire", - // }, - // { - // Type: "color", - // Name: "crit", - // Value: "curacao", - // }, - // { - // Type: "color", - // Name: "err", - // Value: "tiger", - // }, - // { - // Type: "color", - // Name: "warning", - // Value: "pineapple", - // }, - // { - // Type: "color", - // Name: "notice", - // Value: "wolf", - // }, - // { - // Type: "color", - // Name: "info", - // Value: "wolf", - // }, - // { - // Type: "color", - // Name: "debug", - // Value: "wolf", - // }, - // }, - // }, - // { - // Name: "timestamp", - // Position: 3, - // Encodings: []chronograf.ColumnEncoding{ - // - // { - // Type: "visibility", - // Value: "visible", - // }, - // }, - // }, - // { - // Name: "message", - // Position: 2, - // Encodings: []chronograf.ColumnEncoding{ - // - // { - // Type: "visibility", - // Value: "visible", - // }, - // }, - // }, - // { - // Name: "facility", - // Position: 4, - // Encodings: []chronograf.ColumnEncoding{ - // - // { - // Type: "visibility", - // Value: "visible", - // }, - // }, - // }, - // { - // Name: "procid", - // Position: 5, - // Encodings: []chronograf.ColumnEncoding{ - // - // { - // Type: "visibility", - // Value: "hidden", - // }, - // { - // Type: "displayName", - // Value: "ProcID!", - // }, - // }, - // }, - // { - // Name: "appname", - // Position: 6, - // Encodings: []chronograf.ColumnEncoding{ - // { - // Type: "visibility", - // Value: "visible", - // }, - // { - // Type: "displayName", - // Value: "Application", - // }, - // }, - // }, - // { - // Name: "host", - // Position: 7, - // Encodings: []chronograf.ColumnEncoding{ - // { - // Type: "visibility", - // Value: "visible", - // }, - // }, - // }, - // }, - // }, - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - // { - // "links": { - // "self": "\/chronograf\/v1\/org_config\/logviewer" - // }, - // "columns": [ - // { - // "name": "time", - // "position": 0, - // "encodings": [ - // { - // "type": "visibility", - // "value": "hidden" - // } - // ] - // }, - // { - // "name": "severity", - // "position": 1, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // }, - // { - // "type": "label", - // "value": "icon" - // }, - // { - // "type": "color", - // "value": "ruby", - // "name": "emerg" - // }, - // { - // "type": "color", - // "value": "fire", - // "name": "alert" - // }, - // { - // "type": "color", - // "value": "curacao", - // "name": "crit" - // }, - // { - // "type": "color", - // "value": "tiger", - // "name": "err" - // }, - // { - // "type": "color", - // "value": "pineapple", - // "name": "warning" - // }, - // { - // "type": "color", - // "value": "wolf", - // "name": "notice" - // }, - // { - // "type": "color", - // "value": "wolf", - // "name": "info" - // }, - // { - // "type": "color", - // "value": "wolf", - // "name": "debug" - // } - // ] - // }, - // { - // "name": "timestamp", - // "position": 3, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // }, - // { - // "name": "message", - // "position": 2, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // }, - // { - // "name": "facility", - // "position": 4, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // }, - // { - // "name": "procid", - // "position": 5, - // "encodings": [ - // { - // "type": "visibility", - // "value": "hidden" - // }, - // { - // "type": "displayName", - // "value": "ProcID!" - // } - // ] - // }, - // { - // "name": "appname", - // "position": 6, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // }, - // { - // "type": "displayName", - // "value": "Application" - // } - // ] - // }, - // { - // "name": "host", - // "position": 7, - // "encodings": [ - // { - // "type": "visibility", - // "value": "visible" - // } - // ] - // } - // ] - // } - // `, - // }, - // }, - // { - // name: "GET /", - // subName: "signed into default org", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: true, - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "cool", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: true, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/", - // principal: oauth2.Principal{ - // Organization: "default", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "layouts": "/chronograf/v1/layouts", - // "cells": "/chronograf/v2/cells", - // "users": "/chronograf/v1/organizations/default/users", - // "allUsers": "/chronograf/v1/users", - // "organizations": "/chronograf/v1/organizations", - // "mappings": "/chronograf/v1/mappings", - // "sources": "/chronograf/v1/sources", - // "me": "/chronograf/v1/me", - // "environment": "/chronograf/v1/env", - // "dashboards": "/chronograf/v1/dashboards", - // "dashboardsv2":"/chronograf/v2/dashboards", - // "config": { - // "self": "/chronograf/v1/config", - // "auth": "/chronograf/v1/config/auth" - // }, - // "auth": [ - // { - // "name": "github", - // "label": "Github", - // "login": "/oauth/github/login", - // "logout": "/oauth/github/logout", - // "callback": "/oauth/github/callback" - // } - // ], - // "logout": "/oauth/logout", - // "external": { - // "statusFeed": "" - // }, - // "orgConfig": { - // "logViewer": "/chronograf/v1/org_config/logviewer", - // "self": "/chronograf/v1/org_config" - // }, - // "flux": { - // "ast": "/chronograf/v1/flux/ast", - // "self": "/chronograf/v1/flux", - // "suggestions": "/chronograf/v1/flux/suggestions" - // } - //} - //`, - // }, - // }, - // { - // name: "GET /", - // subName: "signed into org 1", - // fields: fields{ - // Config: &chronograf.Config{ - // Auth: chronograf.AuthConfig{ - // SuperAdminNewUsers: true, - // }, - // }, - // Organizations: []chronograf.Organization{ - // { - // ID: "1", - // Name: "cool", - // DefaultRole: roles.ViewerRoleName, - // }, - // }, - // Users: []chronograf.User{ - // { - // ID: 1, // This is artificial, but should be reflective of the users actual ID - // Name: "billibob", - // Provider: "github", - // Scheme: "oauth2", - // SuperAdmin: false, - // Roles: []chronograf.Role{ - // { - // Name: "admin", - // Organization: "default", - // }, - // { - // Name: "member", - // Organization: "1", - // }, - // }, - // }, - // }, - // }, - // args: args{ - // server: &server.Server{ - // GithubClientID: "not empty", - // GithubClientSecret: "not empty", - // }, - // method: "GET", - // path: "/chronograf/v1/", - // principal: oauth2.Principal{ - // Organization: "1", - // Subject: "billibob", - // Issuer: "github", - // }, - // }, - // wants: wants{ - // statusCode: 200, - // body: ` - //{ - // "layouts": "/chronograf/v1/layouts", - // "cells": "/chronograf/v2/cells", - // "users": "/chronograf/v1/organizations/1/users", - // "allUsers": "/chronograf/v1/users", - // "organizations": "/chronograf/v1/organizations", - // "mappings": "/chronograf/v1/mappings", - // "sources": "/chronograf/v1/sources", - // "me": "/chronograf/v1/me", - // "environment": "/chronograf/v1/env", - // "dashboards": "/chronograf/v1/dashboards", - // "dashboardsv2":"/chronograf/v2/dashboards", - // "config": { - // "self": "/chronograf/v1/config", - // "auth": "/chronograf/v1/config/auth" - // }, - // "orgConfig": { - // "logViewer": "/chronograf/v1/org_config/logviewer", - // "self": "/chronograf/v1/org_config" - // }, - // "auth": [ - // { - // "name": "github", - // "label": "Github", - // "login": "/oauth/github/login", - // "logout": "/oauth/github/logout", - // "callback": "/oauth/github/callback" - // } - // ], - // "logout": "/oauth/logout", - // "external": { - // "statusFeed": "" - // }, - // "flux": { - // "ast": "/chronograf/v1/flux/ast", - // "self": "/chronograf/v1/flux", - // "suggestions": "/chronograf/v1/flux/suggestions" - // } - //} - //`, - // }, - // }, - } - - for _, tt := range tests { - testName := fmt.Sprintf("%s: %s", tt.name, tt.subName) - t.Run(testName, func(t *testing.T) { - ctx := context.TODO() - // Create Test Server - host, port := hostAndPort() - tt.args.server.Host = host - tt.args.server.Port = port - - // Use testdata directory for the canned data - tt.args.server.CannedPath = "testdata" - tt.args.server.ResourcesPath = "testdata" - - // This is so that we can use staticly generate jwts - tt.args.server.TokenSecret = "secret" - - // Endpoint for validating RSA256 signatures when using id_token parsing for ADFS - tt.args.server.JwksURL = "" - - boltFile := newBoltFile() - tt.args.server.BoltPath = boltFile - - // Prepopulate BoltDB Database for Server - boltdb := bolt.NewClient() - boltdb.Path = boltFile - - logger := &chronograf.NoopLogger{} - build := chronograf.BuildInfo{ - Version: "pre-1.4.0.0", - Commit: "", - } - _ = boltdb.Open(ctx, logger, build) - - if tt.fields.Config != nil { - if err := boltdb.ConfigStore.Update(ctx, tt.fields.Config); err != nil { - t.Fatalf("failed to update global application config %v", err) - return - } - } - - // Populate Organizations - for i, mapping := range tt.fields.Mappings { - o, err := boltdb.MappingsStore.Add(ctx, &mapping) - if err != nil { - t.Fatalf("failed to add mapping: %v", err) - return - } - tt.fields.Mappings[i] = *o - } - - // Populate Organizations - for i, organization := range tt.fields.Organizations { - o, err := boltdb.OrganizationsStore.Add(ctx, &organization) - if err != nil { - t.Fatalf("failed to add organization: %v", err) - return - } - tt.fields.Organizations[i] = *o - } - - // Populate Users - for i, user := range tt.fields.Users { - u, err := boltdb.UsersStore.Add(ctx, &user) - if err != nil { - t.Fatalf("failed to add user: %v", err) - return - } - tt.fields.Users[i] = *u - } - - // Populate Sources - for i, source := range tt.fields.Sources { - s, err := boltdb.SourcesStore.Add(ctx, source) - if err != nil { - t.Fatalf("failed to add source: %v", err) - return - } - tt.fields.Sources[i] = s - } - - // Populate Servers - for i, server := range tt.fields.Servers { - s, err := boltdb.ServersStore.Add(ctx, server) - if err != nil { - t.Fatalf("failed to add server: %v", err) - return - } - tt.fields.Servers[i] = s - } - - // Populate Layouts - for i, layout := range tt.fields.Layouts { - l, err := boltdb.LayoutsStore.Add(ctx, layout) - if err != nil { - t.Fatalf("failed to add layout: %v", err) - return - } - tt.fields.Layouts[i] = l - } - - // Populate Dashboards - for i, dashboard := range tt.fields.Dashboards { - d, err := boltdb.DashboardsStore.Add(ctx, dashboard) - if err != nil { - t.Fatalf("failed to add dashboard: %v", err) - return - } - tt.fields.Dashboards[i] = d - } - - _ = boltdb.Close() - - go tt.args.server.Serve(ctx) - serverURL := fmt.Sprintf("http://%v:%v%v", host, port, tt.args.path) - - // Wait for the server to come online - timeout := time.Now().Add(30 * time.Second) - for { - _, err := http.Get(serverURL + "/swagger.json") - if err == nil { - break - } - if time.Now().After(timeout) { - t.Fatalf("failed to start server") - return - } - } - - // Set the Expiry time on the principal - tt.args.principal.IssuedAt = time.Now() - tt.args.principal.ExpiresAt = time.Now().Add(10 * time.Second) - - // Construct HTTP Request - buf, _ := json.Marshal(tt.args.payload) - reqBody := ioutil.NopCloser(bytes.NewReader(buf)) - req, _ := http.NewRequest(tt.args.method, serverURL, reqBody) - token, _ := oauth2.NewJWT(tt.args.server.TokenSecret, tt.args.server.JwksURL).Create(ctx, tt.args.principal) - req.AddCookie(&http.Cookie{ - Name: "session", - Value: string(token), - HttpOnly: true, - Path: "/", - }) - - // Make actual http request - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("failed to make httprequest: %v", err) - return - } - - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf( - "%s %s Status Code = %v, want %v", - tt.args.method, - tt.args.path, - resp.StatusCode, - tt.wants.statusCode, - ) - } - - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf( - "%s %s Content Type = %v, want %v", - tt.args.method, - tt.args.path, - content, - tt.wants.contentType, - ) - } - - if eq, err := jsonEqual(tt.wants.body, string(body)); err != nil || !eq { - t.Errorf( - "%s %s Body = %v, want %v", - tt.args.method, - tt.args.path, - string(body), - tt.wants.body, - ) - } - - tt.args.server.Listener.Close() - }) - } -} diff --git a/chronograf/integrations/testdata/example.kap b/chronograf/integrations/testdata/example.kap deleted file mode 100644 index 611216d0811..00000000000 --- a/chronograf/integrations/testdata/example.kap +++ /dev/null @@ -1,8 +0,0 @@ -{ - "id": "5000", - "srcID": "5000", - "name": "Kapa 1", - "url": "http://localhost:9092", - "active": true, - "organization": "howdy" -} diff --git a/chronograf/integrations/testdata/example.org b/chronograf/integrations/testdata/example.org deleted file mode 100644 index 21031e50b16..00000000000 --- a/chronograf/integrations/testdata/example.org +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "howdy", - "name": "An Organization", - "defaultRole": "viewer" -} diff --git a/chronograf/integrations/testdata/example.src b/chronograf/integrations/testdata/example.src deleted file mode 100644 index 2e92c7fc659..00000000000 --- a/chronograf/integrations/testdata/example.src +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "5000", - "name": "Influx 1", - "username": "user1", - "password": "pass1", - "url": "http://localhost:8086", - "metaUrl": "http://metaurl.com", - "type": "influx-enterprise", - "insecureSkipVerify": false, - "default": true, - "telegraf": "telegraf", - "sharedSecret": "cubeapples", - "organization": "howdy" -} diff --git a/chronograf/integrations/testdata/mydash.dashboard b/chronograf/integrations/testdata/mydash.dashboard deleted file mode 100644 index 3e81b46dcef..00000000000 --- a/chronograf/integrations/testdata/mydash.dashboard +++ /dev/null @@ -1,189 +0,0 @@ -{ - "id": 1000, - "cells": [ - { - "i": "8f61c619-dd9b-4761-8aa8-577f27247093", - "x": 0, - "y": 0, - "w": 11, - "h": 5, - "name": "Untitled Cell", - "queries": [ - { - "query": "SELECT mean(\"value\") AS \"mean_value\" FROM \"telegraf\".\"autogen\".\"cpg\" WHERE time \u003e :dashboardTime: GROUP BY time(:interval:) FILL(null)", - "queryConfig": { - "id": "b20baa61-bacb-4a17-b27d-b904a0d18114", - "database": "telegraf", - "measurement": "cpg", - "retentionPolicy": "autogen", - "fields": [ - { - "value": "mean", - "type": "func", - "alias": "mean_value", - "args": [ - { - "value": "value", - "type": "field", - "alias": "" - } - ] - } - ], - "tags": {}, - "groupBy": { - "time": "auto", - "tags": [] - }, - "areTagsAccepted": true, - "fill": "null", - "rawText": null, - "range": null, - "shifts": [] - }, - "source": "/chronograf/v1/sources/2" - } - ], - "axes": { - "x": { - "bounds": [], - "label": "", - "prefix": "", - "suffix": "", - "base": "10", - "scale": "linear" - }, - "y": { - "bounds": [], - "label": "", - "prefix": "", - "suffix": "", - "base": "10", - "scale": "linear" - }, - "y2": { - "bounds": [], - "label": "", - "prefix": "", - "suffix": "", - "base": "10", - "scale": "linear" - } - }, - "type": "line", - "colors": [ - { - "id": "0", - "type": "min", - "hex": "#00C9FF", - "name": "laser", - "value": "0" - }, - { - "id": "1", - "type": "max", - "hex": "#9394FF", - "name": "comet", - "value": "100" - } - ], - "legend": { - "type": "static", - "orientation": "bottom" - } - } - ], - "templates": [ - { - "tempVar": ":dbs:", - "values": [ - { - "value": "_internal", - "type": "database", - "selected": true - }, - { - "value": "telegraf", - "type": "database", - "selected": false - }, - { - "value": "tensorflowdb", - "type": "database", - "selected": false - }, - { - "value": "pushgateway", - "type": "database", - "selected": false - }, - { - "value": "node_exporter", - "type": "database", - "selected": false - }, - { - "value": "mydb", - "type": "database", - "selected": false - }, - { - "value": "tiny", - "type": "database", - "selected": false - }, - { - "value": "blah", - "type": "database", - "selected": false - }, - { - "value": "test", - "type": "database", - "selected": false - }, - { - "value": "chronograf", - "type": "database", - "selected": false - }, - { - "value": "db_name", - "type": "database", - "selected": false - }, - { - "value": "demo", - "type": "database", - "selected": false - }, - { - "value": "eeg", - "type": "database", - "selected": false - }, - { - "value": "solaredge", - "type": "database", - "selected": false - }, - { - "value": "zipkin", - "type": "database", - "selected": false - } - ], - "id": "e7e498bf-5869-4874-9071-24628a2cda63", - "type": "databases", - "label": "", - "query": { - "influxql": "SHOW DATABASES", - "measurement": "", - "tagKey": "", - "fieldKey": "" - } - } - ], - "name": "Name This Dashboard", - "organization": "howdy" - } diff --git a/chronograf/integrations/utils.go b/chronograf/integrations/utils.go deleted file mode 100644 index 2069c09595d..00000000000 --- a/chronograf/integrations/utils.go +++ /dev/null @@ -1,54 +0,0 @@ -package integrations - -import ( - "encoding/json" - "io/ioutil" - "net/http/httptest" - "net/url" - "strconv" - "strings" - - "github.com/google/go-cmp/cmp" -) - -func hostAndPort() (string, int) { - s := httptest.NewServer(nil) - defer s.Close() - - u, err := url.Parse(s.URL) - if err != nil { - panic(err) - } - xs := strings.Split(u.Host, ":") - host := xs[0] - portStr := xs[1] - port, err := strconv.Atoi(portStr) - if err != nil { - panic(err) - } - return host, port - -} - -func newBoltFile() string { - f, err := ioutil.TempFile("", "chronograf-bolt-") - if err != nil { - panic(err) - } - f.Close() - - return f.Name() -} - -func jsonEqual(s1, s2 string) (eq bool, err error) { - var o1, o2 interface{} - - if err = json.Unmarshal([]byte(s1), &o1); err != nil { - return - } - if err = json.Unmarshal([]byte(s2), &o2); err != nil { - return - } - - return cmp.Equal(o1, o2), nil -} diff --git a/chronograf/memdb/kapacitors.go b/chronograf/memdb/kapacitors.go deleted file mode 100644 index 1635942d5db..00000000000 --- a/chronograf/memdb/kapacitors.go +++ /dev/null @@ -1,56 +0,0 @@ -package memdb - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Ensure KapacitorStore implements chronograf.ServersStore. -var _ chronograf.ServersStore = &KapacitorStore{} - -// KapacitorStore implements the chronograf.ServersStore interface, and keeps -// an in-memory Kapacitor according to startup configuration -type KapacitorStore struct { - Kapacitor *chronograf.Server -} - -// All will return a slice containing a configured source -func (store *KapacitorStore) All(ctx context.Context) ([]chronograf.Server, error) { - if store.Kapacitor != nil { - return []chronograf.Server{*store.Kapacitor}, nil - } - return nil, nil -} - -// Add does not have any effect -func (store *KapacitorStore) Add(ctx context.Context, kap chronograf.Server) (chronograf.Server, error) { - return chronograf.Server{}, fmt.Errorf("in-memory KapacitorStore does not support adding a Kapacitor") -} - -// Delete removes the in-memory configured Kapacitor if its ID matches what's provided -func (store *KapacitorStore) Delete(ctx context.Context, kap chronograf.Server) error { - if store.Kapacitor == nil || store.Kapacitor.ID != kap.ID { - return fmt.Errorf("unable to find Kapacitor with id %d", kap.ID) - } - store.Kapacitor = nil - return nil -} - -// Get returns the in-memory Kapacitor if its ID matches what's provided -func (store *KapacitorStore) Get(ctx context.Context, id int) (chronograf.Server, error) { - if store.Kapacitor == nil || store.Kapacitor.ID != id { - return chronograf.Server{}, fmt.Errorf("unable to find Kapacitor with id %d", id) - } - return *store.Kapacitor, nil -} - -// Update overwrites the in-memory configured Kapacitor if its ID matches what's provided -func (store *KapacitorStore) Update(ctx context.Context, kap chronograf.Server) error { - if store.Kapacitor == nil || store.Kapacitor.ID != kap.ID { - return fmt.Errorf("unable to find Kapacitor with id %d", kap.ID) - } - store.Kapacitor = &kap - return nil -} diff --git a/chronograf/memdb/kapacitors_test.go b/chronograf/memdb/kapacitors_test.go deleted file mode 100644 index 74a4cbbae3c..00000000000 --- a/chronograf/memdb/kapacitors_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package memdb - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestInterfaceImplementation(t *testing.T) { - var _ chronograf.ServersStore = &KapacitorStore{} -} - -func TestKapacitorStoreAll(t *testing.T) { - ctx := context.Background() - - store := KapacitorStore{} - kaps, err := store.All(ctx) - if err != nil { - t.Fatal("All should not throw an error with an empty Store") - } - if len(kaps) != 0 { - t.Fatal("Store should be empty") - } - - store.Kapacitor = &chronograf.Server{} - kaps, err = store.All(ctx) - if err != nil { - t.Fatal("All should not throw an error with an empty Store") - } - if len(kaps) != 1 { - t.Fatal("Store should have 1 element") - } -} - -func TestKapacitorStoreAdd(t *testing.T) { - ctx := context.Background() - - store := KapacitorStore{} - _, err := store.Add(ctx, chronograf.Server{}) - if err == nil { - t.Fatal("Store should not support adding another source") - } -} - -func TestKapacitorStoreDelete(t *testing.T) { - ctx := context.Background() - - store := KapacitorStore{} - err := store.Delete(ctx, chronograf.Server{}) - if err == nil { - t.Fatal("Delete should not operate on an empty Store") - } - - store.Kapacitor = &chronograf.Server{ - ID: 9, - } - err = store.Delete(ctx, chronograf.Server{ - ID: 8, - }) - if err == nil { - t.Fatal("Delete should not remove elements with the wrong ID") - } - - err = store.Delete(ctx, chronograf.Server{ - ID: 9, - }) - if err != nil { - t.Fatal("Delete should remove an element with a matching ID") - } -} - -func TestKapacitorStoreGet(t *testing.T) { - ctx := context.Background() - - store := KapacitorStore{} - _, err := store.Get(ctx, 9) - if err == nil { - t.Fatal("Get should return an error for an empty Store") - } - - store.Kapacitor = &chronograf.Server{ - ID: 9, - } - _, err = store.Get(ctx, 8) - if err == nil { - t.Fatal("Get should return an error if it finds no matches") - } - - store.Kapacitor = &chronograf.Server{ - ID: 9, - } - kap, err := store.Get(ctx, 9) - if err != nil || kap.ID != 9 { - t.Fatal("Get should find the element with a matching ID") - } -} - -func TestKapacitorStoreUpdate(t *testing.T) { - ctx := context.Background() - - store := KapacitorStore{} - err := store.Update(ctx, chronograf.Server{}) - if err == nil { - t.Fatal("Update fhouls return an error for an empty Store") - } - - store.Kapacitor = &chronograf.Server{ - ID: 9, - } - err = store.Update(ctx, chronograf.Server{ - ID: 8, - }) - if err == nil { - t.Fatal("Update should return an error if it finds no matches") - } - - store.Kapacitor = &chronograf.Server{ - ID: 9, - } - err = store.Update(ctx, chronograf.Server{ - ID: 9, - URL: "http://crystal.pepsi.com", - }) - if err != nil || store.Kapacitor.URL != "http://crystal.pepsi.com" { - t.Fatal("Update should overwrite elements with matching IDs") - } -} diff --git a/chronograf/memdb/sources.go b/chronograf/memdb/sources.go deleted file mode 100644 index 95f48517dbe..00000000000 --- a/chronograf/memdb/sources.go +++ /dev/null @@ -1,55 +0,0 @@ -package memdb - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Ensure SourcesStore implements chronograf.SourcesStore. -var _ chronograf.SourcesStore = &SourcesStore{} - -// SourcesStore implements the chronograf.SourcesStore interface -type SourcesStore struct { - Source *chronograf.Source -} - -// Add does not have any effect -func (store *SourcesStore) Add(ctx context.Context, src chronograf.Source) (chronograf.Source, error) { - return chronograf.Source{}, fmt.Errorf("in-memory SourcesStore does not support adding a Source") -} - -// All will return a slice containing a configured source -func (store *SourcesStore) All(ctx context.Context) ([]chronograf.Source, error) { - if store.Source != nil { - return []chronograf.Source{*store.Source}, nil - } - return nil, nil -} - -// Delete removes the SourcesStore.Source if it matches the provided Source -func (store *SourcesStore) Delete(ctx context.Context, src chronograf.Source) error { - if store.Source == nil || store.Source.ID != src.ID { - return fmt.Errorf("unable to find Source with id %d", src.ID) - } - store.Source = nil - return nil -} - -// Get returns the configured source if the id matches -func (store *SourcesStore) Get(ctx context.Context, id int) (chronograf.Source, error) { - if store.Source == nil || store.Source.ID != id { - return chronograf.Source{}, fmt.Errorf("unable to find Source with id %d", id) - } - return *store.Source, nil -} - -// Update does nothing -func (store *SourcesStore) Update(ctx context.Context, src chronograf.Source) error { - if store.Source == nil || store.Source.ID != src.ID { - return fmt.Errorf("unable to find Source with id %d", src.ID) - } - store.Source = &src - return nil -} diff --git a/chronograf/memdb/sources_test.go b/chronograf/memdb/sources_test.go deleted file mode 100644 index f5b7a8bf908..00000000000 --- a/chronograf/memdb/sources_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package memdb - -import ( - "context" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestSourcesStore(t *testing.T) { - var _ chronograf.SourcesStore = &SourcesStore{} -} - -func TestSourcesStoreAdd(t *testing.T) { - ctx := context.Background() - - store := SourcesStore{} - _, err := store.Add(ctx, chronograf.Source{}) - if err == nil { - t.Fatal("Store should not support adding another source") - } -} - -func TestSourcesStoreAll(t *testing.T) { - ctx := context.Background() - - store := SourcesStore{} - srcs, err := store.All(ctx) - if err != nil { - t.Fatal("All should not throw an error with an empty Store") - } - if len(srcs) != 0 { - t.Fatal("Store should be empty") - } - - store.Source = &chronograf.Source{} - srcs, err = store.All(ctx) - if err != nil { - t.Fatal("All should not throw an error with an empty Store") - } - if len(srcs) != 1 { - t.Fatal("Store should have 1 element") - } -} - -func TestSourcesStoreDelete(t *testing.T) { - ctx := context.Background() - - store := SourcesStore{} - err := store.Delete(ctx, chronograf.Source{}) - if err == nil { - t.Fatal("Delete should not operate on an empty Store") - } - - store.Source = &chronograf.Source{ - ID: 9, - } - err = store.Delete(ctx, chronograf.Source{ - ID: 8, - }) - if err == nil { - t.Fatal("Delete should not remove elements with the wrong ID") - } - - err = store.Delete(ctx, chronograf.Source{ - ID: 9, - }) - if err != nil { - t.Fatal("Delete should remove an element with a matching ID") - } -} - -func TestSourcesStoreGet(t *testing.T) { - ctx := context.Background() - - store := SourcesStore{} - _, err := store.Get(ctx, 9) - if err == nil { - t.Fatal("Get should return an error for an empty Store") - } - - store.Source = &chronograf.Source{ - ID: 9, - } - _, err = store.Get(ctx, 8) - if err == nil { - t.Fatal("Get should return an error if it finds no matches") - } - - store.Source = &chronograf.Source{ - ID: 9, - } - src, err := store.Get(ctx, 9) - if err != nil || src.ID != 9 { - t.Fatal("Get should find the element with a matching ID") - } -} - -func TestSourcesStoreUpdate(t *testing.T) { - ctx := context.Background() - - store := SourcesStore{} - err := store.Update(ctx, chronograf.Source{}) - if err == nil { - t.Fatal("Update should return an error for an empty Store") - } - - store.Source = &chronograf.Source{ - ID: 9, - } - err = store.Update(ctx, chronograf.Source{ - ID: 8, - }) - if err == nil { - t.Fatal("Update should return an error if it finds no matches") - } - - store.Source = &chronograf.Source{ - ID: 9, - } - err = store.Update(ctx, chronograf.Source{ - ID: 9, - URL: "http://crystal.pepsi.com", - }) - if err != nil || store.Source.URL != "http://crystal.pepsi.com" { - t.Fatal("Update should overwrite elements with matching IDs") - } -} diff --git a/chronograf/mocks/auth.go b/chronograf/mocks/auth.go deleted file mode 100644 index f4302453b11..00000000000 --- a/chronograf/mocks/auth.go +++ /dev/null @@ -1,51 +0,0 @@ -package mocks - -import ( - "context" - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -// Authenticator implements a OAuth2 authenticator -type Authenticator struct { - Principal oauth2.Principal - ValidateErr error - ExtendErr error - Serialized string -} - -// Validate returns Principal associated with authenticated and authorized -// entity if successful. -func (a *Authenticator) Validate(context.Context, *http.Request) (oauth2.Principal, error) { - return a.Principal, a.ValidateErr -} - -// Extend will extend the lifetime of a already validated Principal -func (a *Authenticator) Extend(ctx context.Context, w http.ResponseWriter, p oauth2.Principal) (oauth2.Principal, error) { - cookie := http.Cookie{} - - http.SetCookie(w, &cookie) - return a.Principal, a.ExtendErr -} - -// Authorize will grant privileges to a Principal -func (a *Authenticator) Authorize(ctx context.Context, w http.ResponseWriter, p oauth2.Principal) error { - cookie := http.Cookie{} - - http.SetCookie(w, &cookie) - return nil -} - -// Expire revokes privileges from a Principal -func (a *Authenticator) Expire(http.ResponseWriter) {} - -// ValidAuthorization returns the Principal -func (a *Authenticator) ValidAuthorization(ctx context.Context, serializedAuthorization string) (oauth2.Principal, error) { - return oauth2.Principal{}, nil -} - -// Serialize the serialized values stored on the Authenticator -func (a *Authenticator) Serialize(context.Context, oauth2.Principal) (string, error) { - return a.Serialized, nil -} diff --git a/chronograf/mocks/config.go b/chronograf/mocks/config.go deleted file mode 100644 index 5dd605d947a..00000000000 --- a/chronograf/mocks/config.go +++ /dev/null @@ -1,28 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ConfigStore stores global application configuration -type ConfigStore struct { - Config *chronograf.Config -} - -// Initialize is noop in mocks store -func (c ConfigStore) Initialize(ctx context.Context) error { - return nil -} - -// Get returns the whole global application configuration -func (c ConfigStore) Get(ctx context.Context) (*chronograf.Config, error) { - return c.Config, nil -} - -// Update updates the whole global application configuration -func (c ConfigStore) Update(ctx context.Context, config *chronograf.Config) error { - c.Config = config - return nil -} diff --git a/chronograf/mocks/dashboards.go b/chronograf/mocks/dashboards.go deleted file mode 100644 index f50c2f2f5ae..00000000000 --- a/chronograf/mocks/dashboards.go +++ /dev/null @@ -1,37 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.DashboardsStore = &DashboardsStore{} - -type DashboardsStore struct { - AddF func(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error) - AllF func(ctx context.Context) ([]chronograf.Dashboard, error) - DeleteF func(ctx context.Context, target chronograf.Dashboard) error - GetF func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) - UpdateF func(ctx context.Context, target chronograf.Dashboard) error -} - -func (d *DashboardsStore) Add(ctx context.Context, newDashboard chronograf.Dashboard) (chronograf.Dashboard, error) { - return d.AddF(ctx, newDashboard) -} - -func (d *DashboardsStore) All(ctx context.Context) ([]chronograf.Dashboard, error) { - return d.AllF(ctx) -} - -func (d *DashboardsStore) Delete(ctx context.Context, target chronograf.Dashboard) error { - return d.DeleteF(ctx, target) -} - -func (d *DashboardsStore) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - return d.GetF(ctx, id) -} - -func (d *DashboardsStore) Update(ctx context.Context, target chronograf.Dashboard) error { - return d.UpdateF(ctx, target) -} diff --git a/chronograf/mocks/databases.go b/chronograf/mocks/databases.go deleted file mode 100644 index a5117ed96e9..00000000000 --- a/chronograf/mocks/databases.go +++ /dev/null @@ -1,69 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.Databases = &Databases{} - -// Databases mock allows all databases methods to be set for testing -type Databases struct { - AllDBF func(context.Context) ([]chronograf.Database, error) - ConnectF func(context.Context, *chronograf.Source) error - CreateDBF func(context.Context, *chronograf.Database) (*chronograf.Database, error) - DropDBF func(context.Context, string) error - - AllRPF func(context.Context, string) ([]chronograf.RetentionPolicy, error) - CreateRPF func(context.Context, string, *chronograf.RetentionPolicy) (*chronograf.RetentionPolicy, error) - UpdateRPF func(context.Context, string, string, *chronograf.RetentionPolicy) (*chronograf.RetentionPolicy, error) - DropRPF func(context.Context, string, string) error - - GetMeasurementsF func(ctx context.Context, db string, limit, offset int) ([]chronograf.Measurement, error) -} - -// AllDB lists all databases in the current data source -func (d *Databases) AllDB(ctx context.Context) ([]chronograf.Database, error) { - return d.AllDBF(ctx) -} - -// Connect connects to a database in the current data source -func (d *Databases) Connect(ctx context.Context, src *chronograf.Source) error { - return d.ConnectF(ctx, src) -} - -// CreateDB creates a database in the current data source -func (d *Databases) CreateDB(ctx context.Context, db *chronograf.Database) (*chronograf.Database, error) { - return d.CreateDBF(ctx, db) -} - -// DropDB drops a database in the current data source -func (d *Databases) DropDB(ctx context.Context, db string) error { - return d.DropDBF(ctx, db) -} - -// AllRP lists all retention policies in the current data source -func (d *Databases) AllRP(ctx context.Context, rpX string) ([]chronograf.RetentionPolicy, error) { - return d.AllRPF(ctx, rpX) -} - -// CreateRP creates a retention policy in the current data source -func (d *Databases) CreateRP(ctx context.Context, rpX string, rp *chronograf.RetentionPolicy) (*chronograf.RetentionPolicy, error) { - return d.CreateRPF(ctx, rpX, rp) -} - -// UpdateRP updates a retention policy in the current data source -func (d *Databases) UpdateRP(ctx context.Context, rpX string, rpY string, rp *chronograf.RetentionPolicy) (*chronograf.RetentionPolicy, error) { - return d.UpdateRPF(ctx, rpX, rpY, rp) -} - -// DropRP drops a retention policy in the current data source -func (d *Databases) DropRP(ctx context.Context, rpX string, rpY string) error { - return d.DropRPF(ctx, rpX, rpY) -} - -// GetMeasurements lists measurements in the current data source -func (d *Databases) GetMeasurements(ctx context.Context, db string, limit, offset int) ([]chronograf.Measurement, error) { - return d.GetMeasurementsF(ctx, db, limit, offset) -} diff --git a/chronograf/mocks/kapacitor_client.go b/chronograf/mocks/kapacitor_client.go deleted file mode 100644 index 648952ea7d1..00000000000 --- a/chronograf/mocks/kapacitor_client.go +++ /dev/null @@ -1,34 +0,0 @@ -package mocks - -// TODO(desa): resolve kapacitor dependency - -//var _ kapacitor.KapaClient = &KapaClient{} -// -//// Client is a mock Kapacitor client -//type KapaClient struct { -// CreateTaskF func(opts client.CreateTaskOptions) (client.Task, error) -// DeleteTaskF func(link client.Link) error -// ListTasksF func(opts *client.ListTasksOptions) ([]client.Task, error) -// TaskF func(link client.Link, opts *client.TaskOptions) (client.Task, error) -// UpdateTaskF func(link client.Link, opts client.UpdateTaskOptions) (client.Task, error) -//} -// -//func (p *KapaClient) CreateTask(opts client.CreateTaskOptions) (client.Task, error) { -// return p.CreateTaskF(opts) -//} -// -//func (p *KapaClient) DeleteTask(link client.Link) error { -// return p.DeleteTaskF(link) -//} -// -//func (p *KapaClient) ListTasks(opts *client.ListTasksOptions) ([]client.Task, error) { -// return p.ListTasksF(opts) -//} -// -//func (p *KapaClient) Task(link client.Link, opts *client.TaskOptions) (client.Task, error) { -// return p.TaskF(link, opts) -//} -// -//func (p *KapaClient) UpdateTask(link client.Link, opts client.UpdateTaskOptions) (client.Task, error) { -// return p.UpdateTaskF(link, opts) -//} diff --git a/chronograf/mocks/layouts.go b/chronograf/mocks/layouts.go deleted file mode 100644 index 0f321d27e75..00000000000 --- a/chronograf/mocks/layouts.go +++ /dev/null @@ -1,37 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.LayoutsStore = &LayoutsStore{} - -type LayoutsStore struct { - AddF func(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error) - AllF func(ctx context.Context) ([]chronograf.Layout, error) - DeleteF func(ctx context.Context, layout chronograf.Layout) error - GetF func(ctx context.Context, id string) (chronograf.Layout, error) - UpdateF func(ctx context.Context, layout chronograf.Layout) error -} - -func (s *LayoutsStore) Add(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error) { - return s.AddF(ctx, layout) -} - -func (s *LayoutsStore) All(ctx context.Context) ([]chronograf.Layout, error) { - return s.AllF(ctx) -} - -func (s *LayoutsStore) Delete(ctx context.Context, layout chronograf.Layout) error { - return s.DeleteF(ctx, layout) -} - -func (s *LayoutsStore) Get(ctx context.Context, id string) (chronograf.Layout, error) { - return s.GetF(ctx, id) -} - -func (s *LayoutsStore) Update(ctx context.Context, layout chronograf.Layout) error { - return s.UpdateF(ctx, layout) -} diff --git a/chronograf/mocks/mapping.go b/chronograf/mocks/mapping.go deleted file mode 100644 index ec09bdb4dfe..00000000000 --- a/chronograf/mocks/mapping.go +++ /dev/null @@ -1,35 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -type MappingsStore struct { - AddF func(context.Context, *chronograf.Mapping) (*chronograf.Mapping, error) - AllF func(context.Context) ([]chronograf.Mapping, error) - DeleteF func(context.Context, *chronograf.Mapping) error - UpdateF func(context.Context, *chronograf.Mapping) error - GetF func(context.Context, string) (*chronograf.Mapping, error) -} - -func (s *MappingsStore) Add(ctx context.Context, m *chronograf.Mapping) (*chronograf.Mapping, error) { - return s.AddF(ctx, m) -} - -func (s *MappingsStore) All(ctx context.Context) ([]chronograf.Mapping, error) { - return s.AllF(ctx) -} - -func (s *MappingsStore) Delete(ctx context.Context, m *chronograf.Mapping) error { - return s.DeleteF(ctx, m) -} - -func (s *MappingsStore) Get(ctx context.Context, id string) (*chronograf.Mapping, error) { - return s.GetF(ctx, id) -} - -func (s *MappingsStore) Update(ctx context.Context, m *chronograf.Mapping) error { - return s.UpdateF(ctx, m) -} diff --git a/chronograf/mocks/org_config.go b/chronograf/mocks/org_config.go deleted file mode 100644 index 6f0715f2add..00000000000 --- a/chronograf/mocks/org_config.go +++ /dev/null @@ -1,22 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{} - -type OrganizationConfigStore struct { - FindOrCreateF func(ctx context.Context, id string) (*chronograf.OrganizationConfig, error) - PutF func(ctx context.Context, c *chronograf.OrganizationConfig) error -} - -func (s *OrganizationConfigStore) FindOrCreate(ctx context.Context, id string) (*chronograf.OrganizationConfig, error) { - return s.FindOrCreateF(ctx, id) -} - -func (s *OrganizationConfigStore) Put(ctx context.Context, c *chronograf.OrganizationConfig) error { - return s.PutF(ctx, c) -} diff --git a/chronograf/mocks/organizations.go b/chronograf/mocks/organizations.go deleted file mode 100644 index ddb734a6284..00000000000 --- a/chronograf/mocks/organizations.go +++ /dev/null @@ -1,47 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.OrganizationsStore = &OrganizationsStore{} - -type OrganizationsStore struct { - AllF func(context.Context) ([]chronograf.Organization, error) - AddF func(context.Context, *chronograf.Organization) (*chronograf.Organization, error) - DeleteF func(context.Context, *chronograf.Organization) error - GetF func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) - UpdateF func(context.Context, *chronograf.Organization) error - CreateDefaultF func(context.Context) error - DefaultOrganizationF func(context.Context) (*chronograf.Organization, error) -} - -func (s *OrganizationsStore) CreateDefault(ctx context.Context) error { - return s.CreateDefaultF(ctx) -} - -func (s *OrganizationsStore) DefaultOrganization(ctx context.Context) (*chronograf.Organization, error) { - return s.DefaultOrganizationF(ctx) -} - -func (s *OrganizationsStore) Add(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { - return s.AddF(ctx, o) -} - -func (s *OrganizationsStore) All(ctx context.Context) ([]chronograf.Organization, error) { - return s.AllF(ctx) -} - -func (s *OrganizationsStore) Delete(ctx context.Context, o *chronograf.Organization) error { - return s.DeleteF(ctx, o) -} - -func (s *OrganizationsStore) Get(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return s.GetF(ctx, q) -} - -func (s *OrganizationsStore) Update(ctx context.Context, o *chronograf.Organization) error { - return s.UpdateF(ctx, o) -} diff --git a/chronograf/mocks/roles.go b/chronograf/mocks/roles.go deleted file mode 100644 index 235ae61df10..00000000000 --- a/chronograf/mocks/roles.go +++ /dev/null @@ -1,43 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.RolesStore = &RolesStore{} - -// RolesStore mock allows all functions to be set for testing -type RolesStore struct { - AllF func(context.Context) ([]chronograf.Role, error) - AddF func(context.Context, *chronograf.Role) (*chronograf.Role, error) - DeleteF func(context.Context, *chronograf.Role) error - GetF func(ctx context.Context, name string) (*chronograf.Role, error) - UpdateF func(context.Context, *chronograf.Role) error -} - -// All lists all Roles from the RolesStore -func (s *RolesStore) All(ctx context.Context) ([]chronograf.Role, error) { - return s.AllF(ctx) -} - -// Add a new Role in the RolesStore -func (s *RolesStore) Add(ctx context.Context, u *chronograf.Role) (*chronograf.Role, error) { - return s.AddF(ctx, u) -} - -// Delete the Role from the RolesStore -func (s *RolesStore) Delete(ctx context.Context, u *chronograf.Role) error { - return s.DeleteF(ctx, u) -} - -// Get retrieves a Role if name exists. -func (s *RolesStore) Get(ctx context.Context, name string) (*chronograf.Role, error) { - return s.GetF(ctx, name) -} - -// Update the Role's permissions or users -func (s *RolesStore) Update(ctx context.Context, u *chronograf.Role) error { - return s.UpdateF(ctx, u) -} diff --git a/chronograf/mocks/servers.go b/chronograf/mocks/servers.go deleted file mode 100644 index 9eea65044ff..00000000000 --- a/chronograf/mocks/servers.go +++ /dev/null @@ -1,38 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.ServersStore = &ServersStore{} - -// ServersStore mock allows all functions to be set for testing -type ServersStore struct { - AllF func(context.Context) ([]chronograf.Server, error) - AddF func(context.Context, chronograf.Server) (chronograf.Server, error) - DeleteF func(context.Context, chronograf.Server) error - GetF func(ctx context.Context, ID int) (chronograf.Server, error) - UpdateF func(context.Context, chronograf.Server) error -} - -func (s *ServersStore) All(ctx context.Context) ([]chronograf.Server, error) { - return s.AllF(ctx) -} - -func (s *ServersStore) Add(ctx context.Context, srv chronograf.Server) (chronograf.Server, error) { - return s.AddF(ctx, srv) -} - -func (s *ServersStore) Delete(ctx context.Context, srv chronograf.Server) error { - return s.DeleteF(ctx, srv) -} - -func (s *ServersStore) Get(ctx context.Context, id int) (chronograf.Server, error) { - return s.GetF(ctx, id) -} - -func (s *ServersStore) Update(ctx context.Context, srv chronograf.Server) error { - return s.UpdateF(ctx, srv) -} diff --git a/chronograf/mocks/sources.go b/chronograf/mocks/sources.go deleted file mode 100644 index 5a77b2cb41c..00000000000 --- a/chronograf/mocks/sources.go +++ /dev/null @@ -1,43 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.SourcesStore = &SourcesStore{} - -// SourcesStore mock allows all functions to be set for testing -type SourcesStore struct { - AllF func(context.Context) ([]chronograf.Source, error) - AddF func(context.Context, chronograf.Source) (chronograf.Source, error) - DeleteF func(context.Context, chronograf.Source) error - GetF func(ctx context.Context, ID int) (chronograf.Source, error) - UpdateF func(context.Context, chronograf.Source) error -} - -// All returns all sources in the store -func (s *SourcesStore) All(ctx context.Context) ([]chronograf.Source, error) { - return s.AllF(ctx) -} - -// Add creates a new source in the SourcesStore and returns Source with ID -func (s *SourcesStore) Add(ctx context.Context, src chronograf.Source) (chronograf.Source, error) { - return s.AddF(ctx, src) -} - -// Delete the Source from the store -func (s *SourcesStore) Delete(ctx context.Context, src chronograf.Source) error { - return s.DeleteF(ctx, src) -} - -// Get retrieves Source if `ID` exists -func (s *SourcesStore) Get(ctx context.Context, ID int) (chronograf.Source, error) { - return s.GetF(ctx, ID) -} - -// Update the Source in the store. -func (s *SourcesStore) Update(ctx context.Context, src chronograf.Source) error { - return s.UpdateF(ctx, src) -} diff --git a/chronograf/mocks/store.go b/chronograf/mocks/store.go deleted file mode 100644 index f22df100cdc..00000000000 --- a/chronograf/mocks/store.go +++ /dev/null @@ -1,55 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Store is a server.DataStore -type Store struct { - SourcesStore chronograf.SourcesStore - MappingsStore chronograf.MappingsStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - OrganizationsStore chronograf.OrganizationsStore - ConfigStore chronograf.ConfigStore - OrganizationConfigStore chronograf.OrganizationConfigStore -} - -func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore { - return s.SourcesStore -} - -func (s *Store) Servers(ctx context.Context) chronograf.ServersStore { - return s.ServersStore -} - -func (s *Store) Layouts(ctx context.Context) chronograf.LayoutsStore { - return s.LayoutsStore -} - -func (s *Store) Users(ctx context.Context) chronograf.UsersStore { - return s.UsersStore -} - -func (s *Store) Organizations(ctx context.Context) chronograf.OrganizationsStore { - return s.OrganizationsStore -} -func (s *Store) Mappings(ctx context.Context) chronograf.MappingsStore { - return s.MappingsStore -} - -func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore { - return s.DashboardsStore -} - -func (s *Store) Config(ctx context.Context) chronograf.ConfigStore { - return s.ConfigStore -} - -func (s *Store) OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore { - return s.OrganizationConfigStore -} diff --git a/chronograf/mocks/users.go b/chronograf/mocks/users.go deleted file mode 100644 index 9e3646e1136..00000000000 --- a/chronograf/mocks/users.go +++ /dev/null @@ -1,49 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ chronograf.UsersStore = &UsersStore{} - -// UsersStore mock allows all functions to be set for testing -type UsersStore struct { - AllF func(context.Context) ([]chronograf.User, error) - AddF func(context.Context, *chronograf.User) (*chronograf.User, error) - DeleteF func(context.Context, *chronograf.User) error - GetF func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) - UpdateF func(context.Context, *chronograf.User) error - NumF func(context.Context) (int, error) -} - -// All lists all users from the UsersStore -func (s *UsersStore) All(ctx context.Context) ([]chronograf.User, error) { - return s.AllF(ctx) -} - -// Num returns the number of users in the UsersStore -func (s *UsersStore) Num(ctx context.Context) (int, error) { - return s.NumF(ctx) -} - -// Add a new User in the UsersStore -func (s *UsersStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return s.AddF(ctx, u) -} - -// Delete the User from the UsersStore -func (s *UsersStore) Delete(ctx context.Context, u *chronograf.User) error { - return s.DeleteF(ctx, u) -} - -// Get retrieves a user if name exists. -func (s *UsersStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return s.GetF(ctx, q) -} - -// Update the user's permissions or roles -func (s *UsersStore) Update(ctx context.Context, u *chronograf.User) error { - return s.UpdateF(ctx, u) -} diff --git a/chronograf/multistore/dashboards.go b/chronograf/multistore/dashboards.go deleted file mode 100644 index d5d275ea2b9..00000000000 --- a/chronograf/multistore/dashboards.go +++ /dev/null @@ -1,97 +0,0 @@ -package multistore - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Ensure DashboardsStore implements chronograf.DashboardsStore. -var _ chronograf.DashboardsStore = &DashboardsStore{} - -// DashboardsStore implements the chronograf.DashboardsStore interface, and -// delegates to all contained DashboardsStores -type DashboardsStore struct { - Stores []chronograf.DashboardsStore -} - -// All concatenates the Dashboards of all contained Stores -func (multi *DashboardsStore) All(ctx context.Context) ([]chronograf.Dashboard, error) { - all := []chronograf.Dashboard{} - boardSet := map[chronograf.DashboardID]struct{}{} - - ok := false - var err error - for _, store := range multi.Stores { - var boards []chronograf.Dashboard - boards, err = store.All(ctx) - if err != nil { - // If this Store is unable to return an array of dashboards, skip to the - // next Store. - continue - } - ok = true // We've received a response from at least one Store - for _, board := range boards { - // Enforce that the dashboard has a unique ID - // If the ID has been seen before, ignore the dashboard - if _, okay := boardSet[board.ID]; !okay { // We have a new dashboard - boardSet[board.ID] = struct{}{} // We just care that the ID is unique - all = append(all, board) - } - } - } - if !ok { - return nil, err - } - return all, nil -} - -// Add the dashboard to the first responsive Store -func (multi *DashboardsStore) Add(ctx context.Context, dashboard chronograf.Dashboard) (chronograf.Dashboard, error) { - var err error - for _, store := range multi.Stores { - var d chronograf.Dashboard - d, err = store.Add(ctx, dashboard) - if err == nil { - return d, nil - } - } - return chronograf.Dashboard{}, nil -} - -// Delete delegates to all Stores, returns success if one Store is successful -func (multi *DashboardsStore) Delete(ctx context.Context, dashboard chronograf.Dashboard) error { - var err error - for _, store := range multi.Stores { - err = store.Delete(ctx, dashboard) - if err == nil { - return nil - } - } - return err -} - -// Get finds the Dashboard by id among all contained Stores -func (multi *DashboardsStore) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - var err error - for _, store := range multi.Stores { - var d chronograf.Dashboard - d, err = store.Get(ctx, id) - if err == nil { - return d, nil - } - } - return chronograf.Dashboard{}, nil -} - -// Update the first responsive Store -func (multi *DashboardsStore) Update(ctx context.Context, dashboard chronograf.Dashboard) error { - var err error - for _, store := range multi.Stores { - err = store.Update(ctx, dashboard) - if err == nil { - return nil - } - } - return err -} diff --git a/chronograf/multistore/kapacitors.go b/chronograf/multistore/kapacitors.go deleted file mode 100644 index cd2f96bcb78..00000000000 --- a/chronograf/multistore/kapacitors.go +++ /dev/null @@ -1,97 +0,0 @@ -package multistore - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Ensure KapacitorStore implements chronograf.ServersStore. -var _ chronograf.ServersStore = &KapacitorStore{} - -// KapacitorStore implements the chronograf.ServersStore interface, and -// delegates to all contained KapacitorStores -type KapacitorStore struct { - Stores []chronograf.ServersStore -} - -// All concatenates the Kapacitors of all contained Stores -func (multi *KapacitorStore) All(ctx context.Context) ([]chronograf.Server, error) { - all := []chronograf.Server{} - kapSet := map[int]struct{}{} - - ok := false - var err error - for _, store := range multi.Stores { - var kaps []chronograf.Server - kaps, err = store.All(ctx) - if err != nil { - // If this Store is unable to return an array of kapacitors, skip to the - // next Store. - continue - } - ok = true // We've received a response from at least one Store - for _, kap := range kaps { - // Enforce that the kapacitor has a unique ID - // If the ID has been seen before, ignore the kapacitor - if _, okay := kapSet[kap.ID]; !okay { // We have a new kapacitor - kapSet[kap.ID] = struct{}{} // We just care that the ID is unique - all = append(all, kap) - } - } - } - if !ok { - return nil, err - } - return all, nil -} - -// Add the kap to the first responsive Store -func (multi *KapacitorStore) Add(ctx context.Context, kap chronograf.Server) (chronograf.Server, error) { - var err error - for _, store := range multi.Stores { - var k chronograf.Server - k, err = store.Add(ctx, kap) - if err == nil { - return k, nil - } - } - return chronograf.Server{}, nil -} - -// Delete delegates to all Stores, returns success if one Store is successful -func (multi *KapacitorStore) Delete(ctx context.Context, kap chronograf.Server) error { - var err error - for _, store := range multi.Stores { - err = store.Delete(ctx, kap) - if err == nil { - return nil - } - } - return err -} - -// Get finds the Source by id among all contained Stores -func (multi *KapacitorStore) Get(ctx context.Context, id int) (chronograf.Server, error) { - var err error - for _, store := range multi.Stores { - var k chronograf.Server - k, err = store.Get(ctx, id) - if err == nil { - return k, nil - } - } - return chronograf.Server{}, nil -} - -// Update the first responsive Store -func (multi *KapacitorStore) Update(ctx context.Context, kap chronograf.Server) error { - var err error - for _, store := range multi.Stores { - err = store.Update(ctx, kap) - if err == nil { - return nil - } - } - return err -} diff --git a/chronograf/multistore/kapacitors_test.go b/chronograf/multistore/kapacitors_test.go deleted file mode 100644 index 1bc073a66c8..00000000000 --- a/chronograf/multistore/kapacitors_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package multistore - -import ( - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestInterfaceImplementation(t *testing.T) { - var _ chronograf.ServersStore = &KapacitorStore{} -} diff --git a/chronograf/multistore/layouts.go b/chronograf/multistore/layouts.go deleted file mode 100644 index 900128b2ed7..00000000000 --- a/chronograf/multistore/layouts.go +++ /dev/null @@ -1,94 +0,0 @@ -package multistore - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Layouts is a LayoutsStore that contains multiple LayoutsStores -// The All method will return the set of all Layouts. -// Each method will be tried against the Stores slice serially. -type Layouts struct { - Stores []chronograf.LayoutsStore -} - -// All returns the set of all layouts -func (s *Layouts) All(ctx context.Context) ([]chronograf.Layout, error) { - all := []chronograf.Layout{} - layoutSet := map[string]chronograf.Layout{} - ok := false - var err error - for _, store := range s.Stores { - var layouts []chronograf.Layout - layouts, err = store.All(ctx) - if err != nil { - // Try to load as many layouts as possible - continue - } - ok = true - for _, l := range layouts { - // Enforce that the layout has a unique ID - // If the layout has been seen before then skip - if _, okay := layoutSet[l.ID]; !okay { - layoutSet[l.ID] = l - all = append(all, l) - } - } - } - if !ok { - return nil, err - } - return all, nil -} - -// Add creates a new dashboard in the LayoutsStore. Tries each store sequentially until success. -func (s *Layouts) Add(ctx context.Context, layout chronograf.Layout) (chronograf.Layout, error) { - var err error - for _, store := range s.Stores { - var l chronograf.Layout - l, err = store.Add(ctx, layout) - if err == nil { - return l, nil - } - } - return chronograf.Layout{}, err -} - -// Delete the dashboard from the store. Searches through all stores to find Layout and -// then deletes from that store. -func (s *Layouts) Delete(ctx context.Context, layout chronograf.Layout) error { - var err error - for _, store := range s.Stores { - err = store.Delete(ctx, layout) - if err == nil { - return nil - } - } - return err -} - -// Get retrieves Layout if `ID` exists. Searches through each store sequentially until success. -func (s *Layouts) Get(ctx context.Context, ID string) (chronograf.Layout, error) { - var err error - for _, store := range s.Stores { - var l chronograf.Layout - l, err = store.Get(ctx, ID) - if err == nil { - return l, nil - } - } - return chronograf.Layout{}, err -} - -// Update the dashboard in the store. Searches through each store sequentially until success. -func (s *Layouts) Update(ctx context.Context, layout chronograf.Layout) error { - var err error - for _, store := range s.Stores { - err = store.Update(ctx, layout) - if err == nil { - return nil - } - } - return err -} diff --git a/chronograf/multistore/organizations.go b/chronograf/multistore/organizations.go deleted file mode 100644 index 8bb9289e491..00000000000 --- a/chronograf/multistore/organizations.go +++ /dev/null @@ -1,129 +0,0 @@ -package multistore - -import ( - "context" - "fmt" - "strings" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Ensure OrganizationsStore implements chronograf.OrganizationsStore. -var _ chronograf.OrganizationsStore = &OrganizationsStore{} - -// OrganizationsStore implements the chronograf.OrganizationsStore interface, and -// delegates to all contained OrganizationsStores -type OrganizationsStore struct { - Stores []chronograf.OrganizationsStore -} - -// All concatenates the Organizations of all contained Stores -func (multi *OrganizationsStore) All(ctx context.Context) ([]chronograf.Organization, error) { - all := []chronograf.Organization{} - orgSet := map[string]struct{}{} - - ok := false - var err error - for _, store := range multi.Stores { - var orgs []chronograf.Organization - orgs, err = store.All(ctx) - if err != nil { - // If this Store is unable to return an array of orgs, skip to the - // next Store. - continue - } - ok = true // We've received a response from at least one Store - for _, org := range orgs { - // Enforce that the org has a unique ID - // If the ID has been seen before, ignore the org - if _, okay := orgSet[org.ID]; !okay { // We have a new org - orgSet[org.ID] = struct{}{} // We just care that the ID is unique - all = append(all, org) - } - } - } - if !ok { - return nil, err - } - return all, nil -} - -// Add the org to the first responsive Store -func (multi *OrganizationsStore) Add(ctx context.Context, org *chronograf.Organization) (*chronograf.Organization, error) { - errors := []string{} - for _, store := range multi.Stores { - var o *chronograf.Organization - o, err := store.Add(ctx, org) - if err == nil { - return o, nil - } - errors = append(errors, err.Error()) - } - return nil, fmt.Errorf("unknown error while adding organization: %s", strings.Join(errors, " ")) -} - -// Delete delegates to all Stores, returns success if one Store is successful -func (multi *OrganizationsStore) Delete(ctx context.Context, org *chronograf.Organization) error { - errors := []string{} - for _, store := range multi.Stores { - err := store.Delete(ctx, org) - if err == nil { - return nil - } - errors = append(errors, err.Error()) - } - return fmt.Errorf("unknown error while deleting organization: %s", strings.Join(errors, " ")) -} - -// Get finds the Organization by id among all contained Stores -func (multi *OrganizationsStore) Get(ctx context.Context, query chronograf.OrganizationQuery) (*chronograf.Organization, error) { - var err error - for _, store := range multi.Stores { - var o *chronograf.Organization - o, err = store.Get(ctx, query) - if err == nil { - return o, nil - } - } - return nil, chronograf.ErrOrganizationNotFound -} - -// Update the first responsive Store -func (multi *OrganizationsStore) Update(ctx context.Context, org *chronograf.Organization) error { - errors := []string{} - for _, store := range multi.Stores { - err := store.Update(ctx, org) - if err == nil { - return nil - } - errors = append(errors, err.Error()) - } - return fmt.Errorf("unknown error while updating organization: %s", strings.Join(errors, " ")) -} - -// CreateDefault makes a default organization in the first responsive Store -func (multi *OrganizationsStore) CreateDefault(ctx context.Context) error { - errors := []string{} - for _, store := range multi.Stores { - err := store.CreateDefault(ctx) - if err == nil { - return nil - } - errors = append(errors, err.Error()) - } - return fmt.Errorf("unknown error while creating default organization: %s", strings.Join(errors, " ")) -} - -// DefaultOrganization returns the first successful DefaultOrganization -func (multi *OrganizationsStore) DefaultOrganization(ctx context.Context) (*chronograf.Organization, error) { - errors := []string{} - for _, store := range multi.Stores { - org, err := store.DefaultOrganization(ctx) - if err == nil { - return org, nil - } - errors = append(errors, err.Error()) - } - return nil, fmt.Errorf("unknown error while getting default organization: %s", strings.Join(errors, " ")) - -} diff --git a/chronograf/multistore/sources.go b/chronograf/multistore/sources.go deleted file mode 100644 index 74fd4b99b2c..00000000000 --- a/chronograf/multistore/sources.go +++ /dev/null @@ -1,96 +0,0 @@ -package multistore - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Ensure SourcesStore implements chronograf.SourcesStore. -var _ chronograf.SourcesStore = &SourcesStore{} - -// SourcesStore delegates to the SourcesStores that compose it -type SourcesStore struct { - Stores []chronograf.SourcesStore -} - -// All concatenates the Sources of all contained Stores -func (multi *SourcesStore) All(ctx context.Context) ([]chronograf.Source, error) { - all := []chronograf.Source{} - sourceSet := map[int]struct{}{} - - ok := false - var err error - for _, store := range multi.Stores { - var sources []chronograf.Source - sources, err = store.All(ctx) - if err != nil { - // If this Store is unable to return an array of sources, skip to the - // next Store. - continue - } - ok = true // We've received a response from at least one Store - for _, s := range sources { - // Enforce that the source has a unique ID - // If the source has been seen before, don't override what we already have - if _, okay := sourceSet[s.ID]; !okay { // We have a new Source! - sourceSet[s.ID] = struct{}{} // We just care that the ID is unique - all = append(all, s) - } - } - } - if !ok { - return nil, err - } - return all, nil -} - -// Add the src to the first Store to respond successfully -func (multi *SourcesStore) Add(ctx context.Context, src chronograf.Source) (chronograf.Source, error) { - var err error - for _, store := range multi.Stores { - var s chronograf.Source - s, err = store.Add(ctx, src) - if err == nil { - return s, nil - } - } - return chronograf.Source{}, nil -} - -// Delete delegates to all stores, returns success if one Store is successful -func (multi *SourcesStore) Delete(ctx context.Context, src chronograf.Source) error { - var err error - for _, store := range multi.Stores { - err = store.Delete(ctx, src) - if err == nil { - return nil - } - } - return err -} - -// Get finds the Source by id among all contained Stores -func (multi *SourcesStore) Get(ctx context.Context, id int) (chronograf.Source, error) { - var err error - for _, store := range multi.Stores { - var s chronograf.Source - s, err = store.Get(ctx, id) - if err == nil { - return s, nil - } - } - return chronograf.Source{}, err -} - -// Update the first store to return a successful response -func (multi *SourcesStore) Update(ctx context.Context, src chronograf.Source) error { - var err error - for _, store := range multi.Stores { - err = store.Update(ctx, src) - if err == nil { - return nil - } - } - return err -} diff --git a/chronograf/noop/config.go b/chronograf/noop/config.go deleted file mode 100644 index 2098736f146..00000000000 --- a/chronograf/noop/config.go +++ /dev/null @@ -1,26 +0,0 @@ -package noop - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure ConfigStore implements chronograf.ConfigStore -var _ chronograf.ConfigStore = &ConfigStore{} - -type ConfigStore struct{} - -// TODO(desa): this really should be removed -func (s *ConfigStore) Initialize(context.Context) error { - return fmt.Errorf("cannot initialize") -} - -func (s *ConfigStore) Get(context.Context) (*chronograf.Config, error) { - return nil, chronograf.ErrConfigNotFound -} - -func (s *ConfigStore) Update(context.Context, *chronograf.Config) error { - return fmt.Errorf("cannot update conifg") -} diff --git a/chronograf/noop/dashboards.go b/chronograf/noop/dashboards.go deleted file mode 100644 index cbc06d332af..00000000000 --- a/chronograf/noop/dashboards.go +++ /dev/null @@ -1,33 +0,0 @@ -package noop - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure DashboardsStore implements chronograf.DashboardsStore -var _ chronograf.DashboardsStore = &DashboardsStore{} - -type DashboardsStore struct{} - -func (s *DashboardsStore) All(context.Context) ([]chronograf.Dashboard, error) { - return nil, fmt.Errorf("no dashboards found") -} - -func (s *DashboardsStore) Add(context.Context, chronograf.Dashboard) (chronograf.Dashboard, error) { - return chronograf.Dashboard{}, fmt.Errorf("failed to add dashboard") -} - -func (s *DashboardsStore) Delete(context.Context, chronograf.Dashboard) error { - return fmt.Errorf("failed to delete dashboard") -} - -func (s *DashboardsStore) Get(ctx context.Context, ID chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{}, chronograf.ErrDashboardNotFound -} - -func (s *DashboardsStore) Update(context.Context, chronograf.Dashboard) error { - return fmt.Errorf("failed to update dashboard") -} diff --git a/chronograf/noop/layouts.go b/chronograf/noop/layouts.go deleted file mode 100644 index 9ff2ad4ccf1..00000000000 --- a/chronograf/noop/layouts.go +++ /dev/null @@ -1,33 +0,0 @@ -package noop - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure LayoutsStore implements chronograf.LayoutsStore -var _ chronograf.LayoutsStore = &LayoutsStore{} - -type LayoutsStore struct{} - -func (s *LayoutsStore) All(context.Context) ([]chronograf.Layout, error) { - return nil, fmt.Errorf("no layouts found") -} - -func (s *LayoutsStore) Add(context.Context, chronograf.Layout) (chronograf.Layout, error) { - return chronograf.Layout{}, fmt.Errorf("failed to add layout") -} - -func (s *LayoutsStore) Delete(context.Context, chronograf.Layout) error { - return fmt.Errorf("failed to delete layout") -} - -func (s *LayoutsStore) Get(ctx context.Context, ID string) (chronograf.Layout, error) { - return chronograf.Layout{}, chronograf.ErrLayoutNotFound -} - -func (s *LayoutsStore) Update(context.Context, chronograf.Layout) error { - return fmt.Errorf("failed to update layout") -} diff --git a/chronograf/noop/mappings.go b/chronograf/noop/mappings.go deleted file mode 100644 index 87696839246..00000000000 --- a/chronograf/noop/mappings.go +++ /dev/null @@ -1,33 +0,0 @@ -package noop - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure MappingsStore implements chronograf.MappingsStore -var _ chronograf.MappingsStore = &MappingsStore{} - -type MappingsStore struct{} - -func (s *MappingsStore) All(context.Context) ([]chronograf.Mapping, error) { - return nil, fmt.Errorf("no mappings found") -} - -func (s *MappingsStore) Add(context.Context, *chronograf.Mapping) (*chronograf.Mapping, error) { - return nil, fmt.Errorf("failed to add mapping") -} - -func (s *MappingsStore) Delete(context.Context, *chronograf.Mapping) error { - return fmt.Errorf("failed to delete mapping") -} - -func (s *MappingsStore) Get(ctx context.Context, ID string) (*chronograf.Mapping, error) { - return nil, chronograf.ErrMappingNotFound -} - -func (s *MappingsStore) Update(context.Context, *chronograf.Mapping) error { - return fmt.Errorf("failed to update mapping") -} diff --git a/chronograf/noop/org_config.go b/chronograf/noop/org_config.go deleted file mode 100644 index 53da5ac6a6b..00000000000 --- a/chronograf/noop/org_config.go +++ /dev/null @@ -1,21 +0,0 @@ -package noop - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure OrganizationConfigStore implements chronograf.OrganizationConfigStore -var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{} - -type OrganizationConfigStore struct{} - -func (s *OrganizationConfigStore) FindOrCreate(context.Context, string) (*chronograf.OrganizationConfig, error) { - return nil, chronograf.ErrOrganizationConfigNotFound -} - -func (s *OrganizationConfigStore) Put(context.Context, *chronograf.OrganizationConfig) error { - return fmt.Errorf("cannot replace config") -} diff --git a/chronograf/noop/organizations.go b/chronograf/noop/organizations.go deleted file mode 100644 index 0528bbb6d39..00000000000 --- a/chronograf/noop/organizations.go +++ /dev/null @@ -1,41 +0,0 @@ -package noop - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure OrganizationsStore implements chronograf.OrganizationsStore -var _ chronograf.OrganizationsStore = &OrganizationsStore{} - -type OrganizationsStore struct{} - -func (s *OrganizationsStore) CreateDefault(context.Context) error { - return fmt.Errorf("failed to add organization") -} - -func (s *OrganizationsStore) DefaultOrganization(context.Context) (*chronograf.Organization, error) { - return nil, fmt.Errorf("failed to retrieve default organization") -} - -func (s *OrganizationsStore) All(context.Context) ([]chronograf.Organization, error) { - return nil, fmt.Errorf("no organizations found") -} - -func (s *OrganizationsStore) Add(context.Context, *chronograf.Organization) (*chronograf.Organization, error) { - return nil, fmt.Errorf("failed to add organization") -} - -func (s *OrganizationsStore) Delete(context.Context, *chronograf.Organization) error { - return fmt.Errorf("failed to delete organization") -} - -func (s *OrganizationsStore) Get(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return nil, chronograf.ErrOrganizationNotFound -} - -func (s *OrganizationsStore) Update(context.Context, *chronograf.Organization) error { - return fmt.Errorf("failed to update organization") -} diff --git a/chronograf/noop/servers.go b/chronograf/noop/servers.go deleted file mode 100644 index d6702f5177d..00000000000 --- a/chronograf/noop/servers.go +++ /dev/null @@ -1,33 +0,0 @@ -package noop - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure ServersStore implements chronograf.ServersStore -var _ chronograf.ServersStore = &ServersStore{} - -type ServersStore struct{} - -func (s *ServersStore) All(context.Context) ([]chronograf.Server, error) { - return nil, fmt.Errorf("no servers found") -} - -func (s *ServersStore) Add(context.Context, chronograf.Server) (chronograf.Server, error) { - return chronograf.Server{}, fmt.Errorf("failed to add server") -} - -func (s *ServersStore) Delete(context.Context, chronograf.Server) error { - return fmt.Errorf("failed to delete server") -} - -func (s *ServersStore) Get(ctx context.Context, ID int) (chronograf.Server, error) { - return chronograf.Server{}, chronograf.ErrServerNotFound -} - -func (s *ServersStore) Update(context.Context, chronograf.Server) error { - return fmt.Errorf("failed to update server") -} diff --git a/chronograf/noop/sources.go b/chronograf/noop/sources.go deleted file mode 100644 index 254d9062f43..00000000000 --- a/chronograf/noop/sources.go +++ /dev/null @@ -1,33 +0,0 @@ -package noop - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure SourcesStore implements chronograf.SourcesStore -var _ chronograf.SourcesStore = &SourcesStore{} - -type SourcesStore struct{} - -func (s *SourcesStore) All(context.Context) ([]chronograf.Source, error) { - return nil, fmt.Errorf("no sources found") -} - -func (s *SourcesStore) Add(context.Context, chronograf.Source) (chronograf.Source, error) { - return chronograf.Source{}, fmt.Errorf("failed to add source") -} - -func (s *SourcesStore) Delete(context.Context, chronograf.Source) error { - return fmt.Errorf("failed to delete source") -} - -func (s *SourcesStore) Get(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{}, chronograf.ErrSourceNotFound -} - -func (s *SourcesStore) Update(context.Context, chronograf.Source) error { - return fmt.Errorf("failed to update source") -} diff --git a/chronograf/noop/users.go b/chronograf/noop/users.go deleted file mode 100644 index c65881e7c49..00000000000 --- a/chronograf/noop/users.go +++ /dev/null @@ -1,37 +0,0 @@ -package noop - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure UsersStore implements chronograf.UsersStore -var _ chronograf.UsersStore = &UsersStore{} - -type UsersStore struct{} - -func (s *UsersStore) All(context.Context) ([]chronograf.User, error) { - return nil, fmt.Errorf("no users found") -} - -func (s *UsersStore) Add(context.Context, *chronograf.User) (*chronograf.User, error) { - return nil, fmt.Errorf("failed to add user") -} - -func (s *UsersStore) Delete(context.Context, *chronograf.User) error { - return fmt.Errorf("failed to delete user") -} - -func (s *UsersStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return nil, chronograf.ErrUserNotFound -} - -func (s *UsersStore) Update(context.Context, *chronograf.User) error { - return fmt.Errorf("failed to update user") -} - -func (s *UsersStore) Num(context.Context) (int, error) { - return 0, fmt.Errorf("failed to get number of users") -} diff --git a/chronograf/oauth2/auth0.go b/chronograf/oauth2/auth0.go deleted file mode 100644 index 927cc6d09a0..00000000000 --- a/chronograf/oauth2/auth0.go +++ /dev/null @@ -1,106 +0,0 @@ -package oauth2 - -import ( - "encoding/json" - "net/http" - "net/url" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ Provider = &Auth0{} - -type Auth0 struct { - Generic - Organizations map[string]bool // the set of allowed organizations users may belong to -} - -func (a *Auth0) PrincipalID(provider *http.Client) (string, error) { - type Account struct { - Email string `json:"email"` - Organization string `json:"organization"` - } - - resp, err := provider.Get(a.Generic.APIURL) - if err != nil { - return "", err - } - - defer resp.Body.Close() - act := Account{} - if err = json.NewDecoder(resp.Body).Decode(&act); err != nil { - return "", err - } - - // check for organization membership if required - if len(a.Organizations) > 0 && !a.Organizations[act.Organization] { - a.Logger. - WithField("org", act.Organization). - Error(ErrOrgMembership) - - return "", ErrOrgMembership - } - return act.Email, nil -} - -func (a *Auth0) Group(provider *http.Client) (string, error) { - type Account struct { - Email string `json:"email"` - Organization string `json:"organization"` - } - - resp, err := provider.Get(a.Generic.APIURL) - if err != nil { - return "", err - } - - defer resp.Body.Close() - act := Account{} - if err = json.NewDecoder(resp.Body).Decode(&act); err != nil { - return "", err - } - - return act.Organization, nil -} - -func NewAuth0(auth0Domain, clientID, clientSecret, redirectURL string, organizations []string, logger chronograf.Logger) (Auth0, error) { - domain, err := url.Parse(auth0Domain) - if err != nil { - return Auth0{}, err - } - - domain.Scheme = "https" - - domain.Path = "/authorize" - authURL := domain.String() - - domain.Path = "/oauth/token" - tokenURL := domain.String() - - domain.Path = "/userinfo" - apiURL := domain.String() - - a0 := Auth0{ - Generic: Generic{ - PageName: "auth0", - - ClientID: clientID, - ClientSecret: clientSecret, - - RequiredScopes: []string{"openid", "email"}, - - RedirectURL: redirectURL, - AuthURL: authURL, - TokenURL: tokenURL, - APIURL: apiURL, - - Logger: logger, - }, - Organizations: make(map[string]bool, len(organizations)), - } - - for _, org := range organizations { - a0.Organizations[org] = true - } - return a0, nil -} diff --git a/chronograf/oauth2/auth0_test.go b/chronograf/oauth2/auth0_test.go deleted file mode 100644 index d0ef55bf8ae..00000000000 --- a/chronograf/oauth2/auth0_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package oauth2_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -var auth0Tests = []struct { - name string - email string - organization string // empty string is "no organization" - - allowedUsers []string - allowedOrgs []string // empty disables organization checking - shouldErr bool -}{ - { - "Simple, no orgs", - "marty.mcfly@example.com", - "", - - []string{"marty.mcfly@example.com"}, - []string{}, - false, - }, - { - "Unauthorized", - "marty.mcfly@example.com", - "", - - []string{"doc.brown@example.com"}, - []string{}, - true, - }, - { - "Success - member of an org", - "marty.mcfly@example.com", - "time-travelers", - - []string{"marty.mcfly@example.com"}, - []string{"time-travelers"}, - false, - }, - { - "Failure - not a member of an org", - "marty.mcfly@example.com", - "time-travelers", - - []string{"marty.mcfly@example.com"}, - []string{"biffs-gang"}, - true, - }, -} - -func Test_Auth0_PrincipalID_RestrictsByOrganization(t *testing.T) { - for _, test := range auth0Tests { - t.Run(test.name, func(tt *testing.T) { - tt.Parallel() - expected := struct { - Email string `json:"email"` - Organization string `json:"organization"` - }{ - test.email, - test.organization, - } - - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/userinfo" { - rw.WriteHeader(http.StatusNotFound) - return - } - - allowed := false - for _, user := range test.allowedUsers { - if test.email == user { - allowed = true - } - } - - if !allowed { - rw.WriteHeader(http.StatusUnauthorized) - return - } - - enc := json.NewEncoder(rw) - - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(expected) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov, err := oauth2.NewAuth0(mockAPI.URL, "id", "secret", mockAPI.URL+"/callback", test.allowedOrgs, logger) - if err != nil { - tt.Fatal("Unexpected error instantiating Auth0 provider: err:", err) - } - - tripper, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - tt.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tripper, - } - - var email string - email, err = prov.PrincipalID(tc) - if !test.shouldErr { - if err != nil { - tt.Fatal(test.name, ": Unexpected error while attempting to authenticate: err:", err) - } - - if email != test.email { - tt.Fatal(test.name, ": email mismatch. Got", email, "want:", test.email) - } - } - - if err == nil && test.shouldErr { - tt.Fatal(test.name, ": Expected error while attempting to authenticate but received none") - } - }) - } -} - -func Test_Auth0_ErrsWithBadDomain(t *testing.T) { - t.Parallel() - - logger := &chronograf.NoopLogger{} - _, err := oauth2.NewAuth0("!!@#$!@$%@#$%", "id", "secret", "http://example.com", []string{}, logger) - if err == nil { - t.Fatal("Expected err with bad domain but received none") - } -} diff --git a/chronograf/oauth2/cookies.go b/chronograf/oauth2/cookies.go deleted file mode 100644 index 097240ce1f8..00000000000 --- a/chronograf/oauth2/cookies.go +++ /dev/null @@ -1,135 +0,0 @@ -package oauth2 - -import ( - "context" - "net/http" - "time" -) - -const ( - // DefaultCookieName is the name of the stored cookie - DefaultCookieName = "session" - // DefaultInactivityDuration is the duration a token is valid without any new activity - DefaultInactivityDuration = 5 * time.Minute -) - -var _ Authenticator = &cookie{} - -// cookie represents the location and expiration time of new cookies. -type cookie struct { - Name string // Name is the name of the cookie stored on the browser - Lifespan time.Duration // Lifespan is the expiration date of the cookie. 0 means session cookie - Inactivity time.Duration // Inactivity is the length of time a token is valid if there is no activity - Now func() time.Time - Tokens Tokenizer -} - -// NewCookieJWT creates an Authenticator that uses cookies for auth -func NewCookieJWT(secret string, lifespan time.Duration) Authenticator { - inactivity := DefaultInactivityDuration - // Server interprets a token duration longer than the cookie lifespan as - // a token that was issued by a server with a longer auth-duration and is - // thus invalid, as a security precaution. So, inactivity must be set to - // be less than lifespan. - if lifespan > 0 && inactivity > lifespan { - inactivity = lifespan / 2 // half of the lifespan ensures tokens can be refreshed once. - } - return &cookie{ - Name: DefaultCookieName, - Lifespan: lifespan, - Inactivity: inactivity, - Now: DefaultNowTime, - Tokens: &JWT{ - Secret: secret, - Now: DefaultNowTime, - }, - } -} - -// Validate returns Principal of the Cookie if the Token is valid. -func (c *cookie) Validate(ctx context.Context, r *http.Request) (Principal, error) { - cookie, err := r.Cookie(c.Name) - if err != nil { - return Principal{}, ErrAuthentication - } - - return c.Tokens.ValidPrincipal(ctx, Token(cookie.Value), c.Lifespan) -} - -// Extend will extend the lifetime of the Token by the Inactivity time. Assumes -// Principal is already valid. -func (c *cookie) Extend(ctx context.Context, w http.ResponseWriter, p Principal) (Principal, error) { - // Refresh the token by extending its life another Inactivity duration - p, err := c.Tokens.ExtendedPrincipal(ctx, p, c.Inactivity) - if err != nil { - return Principal{}, ErrAuthentication - } - - // Creating a new token with the extended principal - token, err := c.Tokens.Create(ctx, p) - if err != nil { - return Principal{}, ErrAuthentication - } - - // Cookie lifespan can be indirectly figured out by taking the token's - // issued at time and adding the lifespan setting The token's issued at - // time happens to correspond to the cookie's original issued at time. - exp := p.IssuedAt.Add(c.Lifespan) - // Once the token has been extended, write it out as a new cookie. - c.setCookie(w, string(token), exp) - - return p, nil -} - -// Authorize will create cookies containing token information. It'll create -// a token with cookie.Duration of life to be stored as the cookie's value. -func (c *cookie) Authorize(ctx context.Context, w http.ResponseWriter, p Principal) error { - // Principal will be issued at Now() and will expire - // c.Inactivity into the future - now := c.Now() - p.IssuedAt = now - p.ExpiresAt = now.Add(c.Inactivity) - - token, err := c.Tokens.Create(ctx, p) - if err != nil { - return err - } - - // The time when the cookie expires - exp := now.Add(c.Lifespan) - c.setCookie(w, string(token), exp) - - return nil -} - -// setCookie creates a cookie with value expiring at exp and writes it as a cookie into the response -func (c *cookie) setCookie(w http.ResponseWriter, value string, exp time.Time) { - // Cookie has a Token baked into it - cookie := http.Cookie{ - Name: DefaultCookieName, - Value: value, - HttpOnly: true, - Path: "/", - } - - // Only set a cookie to be persistent (endure beyond the browser session) - // if auth duration is greater than zero - if c.Lifespan > 0 { - cookie.Expires = exp - } - http.SetCookie(w, &cookie) -} - -// Expire returns a cookie that will expire an existing cookie -func (c *cookie) Expire(w http.ResponseWriter) { - // to expire cookie set the time in the past - cookie := http.Cookie{ - Name: DefaultCookieName, - Value: "none", - HttpOnly: true, - Path: "/", - Expires: c.Now().Add(-1 * time.Hour), - } - - http.SetCookie(w, &cookie) -} diff --git a/chronograf/oauth2/cookies_test.go b/chronograf/oauth2/cookies_test.go deleted file mode 100644 index 004d0836d48..00000000000 --- a/chronograf/oauth2/cookies_test.go +++ /dev/null @@ -1,296 +0,0 @@ -package oauth2 - -import ( - "context" - "fmt" - "log" - "net/http" - "net/http/httptest" - "reflect" - "strings" - "testing" - "time" - - gojwt "github.com/dgrijalva/jwt-go" -) - -type MockTokenizer struct { - Principal Principal - ValidErr error - Token Token - CreateErr error - ExtendErr error -} - -func (m *MockTokenizer) ValidPrincipal(ctx context.Context, token Token, duration time.Duration) (Principal, error) { - return m.Principal, m.ValidErr -} - -func (m *MockTokenizer) Create(ctx context.Context, p Principal) (Token, error) { - return m.Token, m.CreateErr -} - -func (m *MockTokenizer) ExtendedPrincipal(ctx context.Context, principal Principal, extension time.Duration) (Principal, error) { - return principal, m.ExtendErr -} - -func (m *MockTokenizer) GetClaims(tokenString string) (gojwt.MapClaims, error) { - return gojwt.MapClaims{}, nil -} - -func TestCookieAuthorize(t *testing.T) { - var test = []struct { - Desc string - Value string - Expected string - Err error - CreateErr error - }{ - { - Desc: "Unable to create token", - Err: ErrAuthentication, - CreateErr: ErrAuthentication, - }, - { - Desc: "Cookie token extracted", - Value: "reallyimportant", - Expected: "reallyimportant", - Err: nil, - }, - } - for _, test := range test { - cook := cookie{ - Lifespan: 1 * time.Second, - Now: func() time.Time { - return time.Unix(0, 0) - }, - Tokens: &MockTokenizer{ - Token: Token(test.Value), - CreateErr: test.CreateErr, - }, - } - principal := Principal{} - w := httptest.NewRecorder() - err := cook.Authorize(context.Background(), w, principal) - if err != test.Err { - t.Fatalf("Cookie extract error; expected %v actual %v", test.Err, err) - } - if test.Err != nil { - continue - } - - cookies := w.Header()["Set-Cookie"] - - if len(cookies) == 0 { - t.Fatal("Expected some cookies but got zero") - } - log.Printf("%s", cookies[0]) - if !strings.Contains(cookies[0], fmt.Sprintf("%s=%s", DefaultCookieName, test.Expected)) { - t.Errorf("Token extract error; expected %v actual %v", test.Expected, principal.Subject) - } - } -} - -func TestCookieValidate(t *testing.T) { - var test = []struct { - Desc string - Name string - Value string - Lookup string - Expected string - Err error - ValidErr error - }{ - { - Desc: "No cookie of this name", - Name: "Auth", - Value: "reallyimportant", - Lookup: "Doesntexist", - Expected: "", - Err: ErrAuthentication, - }, - { - Desc: "Unable to create token", - Name: "Auth", - Lookup: "Auth", - Err: ErrAuthentication, - ValidErr: ErrAuthentication, - }, - { - Desc: "Cookie token extracted", - Name: "Auth", - Value: "reallyimportant", - Lookup: "Auth", - Expected: "reallyimportant", - Err: nil, - }, - } - for _, test := range test { - req, _ := http.NewRequest("", "http://howdy.com", nil) - req.AddCookie(&http.Cookie{ - Name: test.Name, - Value: test.Value, - }) - - cook := cookie{ - Name: test.Lookup, - Lifespan: 1 * time.Second, - Inactivity: DefaultInactivityDuration, - Now: func() time.Time { - return time.Unix(0, 0) - }, - Tokens: &MockTokenizer{ - Principal: Principal{ - Subject: test.Value, - }, - ValidErr: test.ValidErr, - }, - } - principal, err := cook.Validate(context.Background(), req) - if err != test.Err { - t.Errorf("Cookie extract error; expected %v actual %v", test.Err, err) - } - - if principal.Subject != test.Expected { - t.Errorf("Token extract error; expected %v actual %v", test.Expected, principal.Subject) - } - } -} - -func TestNewCookieJWT(t *testing.T) { - auth := NewCookieJWT("secret", 2*time.Second) - if cookie, ok := auth.(*cookie); !ok { - t.Errorf("NewCookieJWT() did not create cookie Authenticator") - } else if cookie.Inactivity != time.Second { - t.Errorf("NewCookieJWT() inactivity was not two seconds: %s", cookie.Inactivity) - } - - auth = NewCookieJWT("secret", time.Hour) - if cookie, ok := auth.(*cookie); !ok { - t.Errorf("NewCookieJWT() did not create cookie Authenticator") - } else if cookie.Inactivity != DefaultInactivityDuration { - t.Errorf("NewCookieJWT() inactivity was not five minutes: %s", cookie.Inactivity) - } - - auth = NewCookieJWT("secret", 0) - if cookie, ok := auth.(*cookie); !ok { - t.Errorf("NewCookieJWT() did not create cookie Authenticator") - } else if cookie.Inactivity != DefaultInactivityDuration { - t.Errorf("NewCookieJWT() inactivity was not five minutes: %s", cookie.Inactivity) - } -} - -func TestCookieExtend(t *testing.T) { - history := time.Unix(-446774400, 0) - type fields struct { - Name string - Lifespan time.Duration - Inactivity time.Duration - Now func() time.Time - Tokens Tokenizer - } - type args struct { - ctx context.Context - w *httptest.ResponseRecorder - p Principal - } - tests := []struct { - name string - fields fields - args args - want Principal - wantErr bool - }{ - { - name: "Successful extention", - want: Principal{ - Subject: "subject", - }, - fields: fields{ - Name: "session", - Lifespan: time.Second, - Inactivity: time.Second, - Now: func() time.Time { - return history - }, - Tokens: &MockTokenizer{ - Principal: Principal{ - Subject: "subject", - }, - Token: "token", - ExtendErr: nil, - }, - }, - args: args{ - ctx: context.Background(), - w: httptest.NewRecorder(), - p: Principal{ - Subject: "subject", - }, - }, - }, - { - name: "Unable to extend", - wantErr: true, - fields: fields{ - Tokens: &MockTokenizer{ - ExtendErr: fmt.Errorf("bad extend"), - }, - }, - args: args{ - ctx: context.Background(), - w: httptest.NewRecorder(), - p: Principal{ - Subject: "subject", - }, - }, - }, - { - name: "Unable to create", - wantErr: true, - fields: fields{ - Tokens: &MockTokenizer{ - CreateErr: fmt.Errorf("bad extend"), - }, - }, - args: args{ - ctx: context.Background(), - w: httptest.NewRecorder(), - p: Principal{ - Subject: "subject", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &cookie{ - Name: tt.fields.Name, - Lifespan: tt.fields.Lifespan, - Inactivity: tt.fields.Inactivity, - Now: tt.fields.Now, - Tokens: tt.fields.Tokens, - } - got, err := c.Extend(tt.args.ctx, tt.args.w, tt.args.p) - if (err != nil) != tt.wantErr { - t.Errorf("cookie.Extend() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !tt.wantErr { - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("cookie.Extend() = %v, want %v", got, tt.want) - } - - cookies := tt.args.w.Header()["Set-Cookie"] - if len(cookies) == 0 { - t.Fatal("Expected some cookies but got zero") - } - log.Printf("%s", cookies) - want := fmt.Sprintf("%s=%s", DefaultCookieName, "token") - if !strings.Contains(cookies[0], want) { - t.Errorf("cookie.Extend() = %v, want %v", cookies[0], want) - } - } - }) - } -} diff --git a/chronograf/oauth2/doc.go b/chronograf/oauth2/doc.go deleted file mode 100644 index 51b4754a8f1..00000000000 --- a/chronograf/oauth2/doc.go +++ /dev/null @@ -1,141 +0,0 @@ -// Package oauth2 provides http.Handlers necessary for implementing Oauth2 -// authentication with multiple Providers. -// -// This is how the pieces of this package fit together: -// -// ┌────────────────────────────────────────┐ -// │github.com/influxdata/influxdb/chronograf/oauth2 │ -// ├────────────────────────────────────────┴────────────────────────────────────┐ -// │┌────────────────────┐ │ -// ││ <> │ ┌─────────────────────────┐ │ -// ││ Authenticator │ │ AuthMux │ │ -// │├────────────────────┤ ├─────────────────────────┤ │ -// ││Authorize() │ Auth │+SuccessURL : string │ │ -// ││Validate() ◀────────│+FailureURL : string │──────────┐ │ -// ||Expire() | |+Now : func() time.Time | | | -// │└──────────△─────────┘ └─────────────────────────┘ | | -// │ │ │ │ | -// │ │ │ │ │ -// │ │ │ │ │ -// │ │ Provider│ │ │ -// │ │ ┌───┘ │ │ -// │┌──────────┴────────────┐ │ ▽ │ -// ││ Tokenizer │ │ ┌───────────────┐ │ -// │├───────────────────────┤ ▼ │ <> │ │ -// ││Create() │ ┌───────────────┐ │ OAuth2Mux │ │ -// ││ValidPrincipal() │ │ <> │ ├───────────────┤ │ -// │└───────────────────────┘ │ Provider │ │Login() │ │ -// │ ├───────────────┤ │Logout() │ │ -// │ │ID() │ │Callback() │ │ -// │ │Scopes() │ └───────────────┘ │ -// │ │Secret() │ │ -// │ │Authenticator()│ │ -// │ └───────────────┘ │ -// │ △ │ -// │ │ │ -// │ ┌─────────────────────────┼─────────────────────────┐ │ -// │ │ │ │ │ -// │ │ │ │ │ -// │ │ │ │ │ -// │ ┌───────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐│ -// │ │ Github │ │ Google │ │ Heroku ││ -// │ ├───────────────────────┤ ├──────────────────────┤ ├──────────────────────┤│ -// │ │+ClientID : string │ │+ClientID : string │ │+ClientID : string ││ -// │ │+ClientSecret : string │ │+ClientSecret : string│ │+ClientSecret : string││ -// │ │+Orgs : []string │ │+Domains : []string │ └──────────────────────┘│ -// │ └───────────────────────┘ │+RedirectURL : string │ │ -// │ └──────────────────────┘ │ -// └─────────────────────────────────────────────────────────────────────────────┘ -// -// The design focuses on an Authenticator, a Provider, and an OAuth2Mux. Their -// responsibilities, respectively, are to decode and encode secrets received -// from a Provider, to perform Provider specific operations in order to extract -// information about a user, and to produce the handlers which persist secrets. -// To add a new provider, You need only implement the Provider interface, and -// add its endpoints to the server Mux. -// -// The Oauth2 flow between a browser, backend, and a Provider that this package -// implements is pictured below for reference. -// -// ┌─────────┐ ┌───────────┐ ┌────────┐ -// │ Browser │ │Chronograf │ │Provider│ -// └─────────┘ └───────────┘ └────────┘ -// │ │ │ -// ├─────── GET /auth ─────────▶ │ -// │ │ │ -// │ │ │ -// ◀ ─ ─ ─302 to Provider ─ ─ ┤ │ -// │ │ │ -// │ │ │ -// ├──────────────── GET /auth w/ callback ─────────────────────▶ -// │ │ │ -// │ │ │ -// ◀─ ─ ─ ─ ─ ─ ─ 302 to Chronograf Callback ─ ─ ─ ─ ─ ─ ─ ─ ┤ -// │ │ │ -// │ Code and State from │ │ -// │ Provider │ │ -// ├───────────────────────────▶ Request token w/ code & │ -// │ │ state │ -// │ ├────────────────────────────────▶ -// │ │ │ -// │ │ Response with │ -// │ │ Token │ -// │ Set cookie, Redirect │◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤ -// │ to / │ │ -// ◀───────────────────────────┤ │ -// │ │ │ -// │ │ │ -// │ │ │ -// │ │ │ -// -// The browser ultimately receives a cookie from Chronograf, authorizing it. -// Its contents are encoded as a JWT whose "sub" claim is the user's email -// address for whatever provider they have authenticated with. Each request to -// Chronograf will validate the contents of this JWT against the `TOKEN_SECRET` -// and checked for expiration. The JWT's "sub" becomes the -// https://en.wikipedia.org/wiki/Principal_(computer_security) used for -// authorization to resources. -// -// The Mux is responsible for providing three http.Handlers for servicing the -// above interaction. These are mounted at specific endpoints by convention -// shared with the front end. Any future Provider routes should follow the same -// convention to ensure compatibility with the front end logic. These routes -// and their responsibilities are: -// -// /oauth/{provider}/login -// -// The `/oauth` endpoint redirects to the Provider for OAuth. Chronograf sets -// the OAuth `state` request parameter to a JWT with a random "sub". Using -// $TOKEN_SECRET `/oauth/github/callback` can validate the `state` parameter -// without needing `state` to be saved. -// -// /oauth/{provider}/callback -// -// The `/oauth/github/callback` receives the OAuth `authorization code` and `state`. -// -// First, it will validate the `state` JWT from the `/oauth` endpoint. `JWT` validation -// only requires access to the signature token. Therefore, there is no need for `state` -// to be saved. Additionally, multiple Chronograf servers will not need to share third -// party storage to synchronize `state`. If this validation fails, the request -// will be redirected to `/login`. -// -// Secondly, the endpoint will use the `authorization code` to retrieve a valid OAuth token -// with the `user:email` scope. If unable to get a token from Github, the request will -// be redirected to `/login`. -// -// Finally, the endpoint will attempt to get the primary email address of the -// user. Again, if not successful, the request will redirect to `/login`. -// -// The email address is used as the subject claim for a new JWT. This JWT becomes the -// value of the cookie sent back to the browser. The cookie is valid for thirty days. -// -// Next, the request is redirected to `/`. -// -// For all API calls to `/chronograf/v1`, the server checks for the existence and validity -// of the JWT within the cookie value. -// If the request did not have a valid JWT, the API returns `HTTP/1.1 401 Unauthorized`. -// -// /oauth/{provider}/logout -// -// Simply expires the session cookie and redirects to `/`. -package oauth2 diff --git a/chronograf/oauth2/generic.go b/chronograf/oauth2/generic.go deleted file mode 100644 index 8483c7f4fa0..00000000000 --- a/chronograf/oauth2/generic.go +++ /dev/null @@ -1,230 +0,0 @@ -package oauth2 - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "strings" - - gojwt "github.com/dgrijalva/jwt-go" - "github.com/influxdata/influxdb/v2/chronograf" - "golang.org/x/oauth2" -) - -// ExtendedProvider extendts the base Provider interface with optional methods -type ExtendedProvider interface { - Provider - // get PrincipalID from id_token - PrincipalIDFromClaims(claims gojwt.MapClaims) (string, error) - GroupFromClaims(claims gojwt.MapClaims) (string, error) -} - -var _ ExtendedProvider = &Generic{} - -// Generic provides OAuth Login and Callback server and is modeled -// after the Github OAuth2 provider. Callback will set an authentication -// cookie. This cookie's value is a JWT containing the user's primary -// email address. -type Generic struct { - PageName string // Name displayed on the login page - ClientID string - ClientSecret string - RequiredScopes []string - Domains []string // Optional email domain checking - RedirectURL string - AuthURL string - TokenURL string - APIURL string // APIURL returns OpenID Userinfo - APIKey string // APIKey is the JSON key to lookup email address in APIURL response - Logger chronograf.Logger -} - -// Name is the name of the provider -func (g *Generic) Name() string { - if g.PageName == "" { - return "generic" - } - return g.PageName -} - -// ID returns the generic application client id -func (g *Generic) ID() string { - return g.ClientID -} - -// Secret returns the generic application client secret -func (g *Generic) Secret() string { - return g.ClientSecret -} - -// Scopes for generic provider required of the client. -func (g *Generic) Scopes() []string { - return g.RequiredScopes -} - -// Config is the Generic OAuth2 exchange information and endpoints -func (g *Generic) Config() *oauth2.Config { - return &oauth2.Config{ - ClientID: g.ID(), - ClientSecret: g.Secret(), - Scopes: g.Scopes(), - RedirectURL: g.RedirectURL, - Endpoint: oauth2.Endpoint{ - AuthURL: g.AuthURL, - TokenURL: g.TokenURL, - }, - } -} - -// PrincipalID returns the email address of the user. -func (g *Generic) PrincipalID(provider *http.Client) (string, error) { - res := map[string]interface{}{} - - r, err := provider.Get(g.APIURL) - if err != nil { - return "", err - } - - defer r.Body.Close() - if err = json.NewDecoder(r.Body).Decode(&res); err != nil { - return "", err - } - - email := "" - value := res[g.APIKey] - if e, ok := value.(string); ok { - email = e - } - - // If we did not receive an email address, try to lookup the email - // in a similar way as github - if email == "" { - email, err = g.getPrimaryEmail(provider) - if err != nil { - return "", err - } - } - - // If we need to restrict to a set of domains, we first get the org - // and filter. - if len(g.Domains) > 0 { - // If not in the domain deny permission - if ok := ofDomain(g.Domains, email); !ok { - g.Logger.Error("Not a member of required domain.") - return "", fmt.Errorf("not a member of required domain") - } - } - - return email, nil -} - -// Group returns the domain that a user belongs to in the -// the generic OAuth. -func (g *Generic) Group(provider *http.Client) (string, error) { - res := map[string]interface{}{} - - r, err := provider.Get(g.APIURL) - if err != nil { - return "", err - } - - defer r.Body.Close() - if err = json.NewDecoder(r.Body).Decode(&res); err != nil { - return "", err - } - - email := "" - value := res[g.APIKey] - if e, ok := value.(string); ok { - email = e - } - - // If we did not receive an email address, try to lookup the email - // in a similar way as github - if email == "" { - email, err = g.getPrimaryEmail(provider) - if err != nil { - return "", err - } - } - - domain := strings.Split(email, "@") - if len(domain) != 2 { - return "", fmt.Errorf("malformed email address, expected %q to contain @ symbol", email) - } - - return domain[1], nil -} - -// UserEmail represents user's email address -type UserEmail struct { - Email *string `json:"email,omitempty"` - Primary *bool `json:"primary,omitempty"` - Verified *bool `json:"verified,omitempty"` -} - -// getPrimaryEmail gets the private email account for the authenticated user. -func (g *Generic) getPrimaryEmail(client *http.Client) (string, error) { - emailsEndpoint := g.APIURL + "/emails" - r, err := client.Get(emailsEndpoint) - if err != nil { - return "", err - } - defer r.Body.Close() - - emails := []*UserEmail{} - if err = json.NewDecoder(r.Body).Decode(&emails); err != nil { - return "", err - } - - email, err := g.primaryEmail(emails) - if err != nil { - g.Logger.Error("Unable to retrieve primary email ", err.Error()) - return "", err - } - return email, nil -} - -func (g *Generic) primaryEmail(emails []*UserEmail) (string, error) { - for _, m := range emails { - if m != nil && m.Primary != nil && m.Verified != nil && m.Email != nil { - return *m.Email, nil - } - } - return "", errors.New("no primary email address") -} - -// ofDomain makes sure that the email is in one of the required domains -func ofDomain(requiredDomains []string, email string) bool { - for _, domain := range requiredDomains { - emailDomain := fmt.Sprintf("@%s", domain) - if strings.HasSuffix(email, emailDomain) { - return true - } - } - return false -} - -// PrincipalIDFromClaims verifies an optional id_token and extracts email address of the user -func (g *Generic) PrincipalIDFromClaims(claims gojwt.MapClaims) (string, error) { - if id, ok := claims[g.APIKey].(string); ok { - return id, nil - } - return "", fmt.Errorf("no claim for %s", g.APIKey) -} - -// GroupFromClaims verifies an optional id_token, extracts the email address of the user and splits off the domain part -func (g *Generic) GroupFromClaims(claims gojwt.MapClaims) (string, error) { - if id, ok := claims[g.APIKey].(string); ok { - email := strings.Split(id, "@") - if len(email) != 2 { - g.Logger.Error("malformed email address, expected %q to contain @ symbol", id) - return "DEFAULT", nil - } - - return email[1], nil - } - - return "", fmt.Errorf("no claim for %s", g.APIKey) -} diff --git a/chronograf/oauth2/generic_test.go b/chronograf/oauth2/generic_test.go deleted file mode 100644 index e54e8aadbcb..00000000000 --- a/chronograf/oauth2/generic_test.go +++ /dev/null @@ -1,200 +0,0 @@ -package oauth2_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -func TestGenericGroup_withNotEmail(t *testing.T) { - t.Parallel() - - response := struct { - Email string `json:"not-email"` - }{ - "martymcfly@pinheads.rok", - } - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - rw.WriteHeader(http.StatusNotFound) - return - } - enc := json.NewEncoder(rw) - - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(response) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Generic{ - Logger: logger, - APIURL: mockAPI.URL, - APIKey: "not-email", - } - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - got, err := prov.Group(tc) - if err != nil { - t.Fatal("Unexpected error while retrieving PrincipalID: err:", err) - } - - want := "pinheads.rok" - if got != want { - t.Fatal("Retrieved group was not as expected. Want:", want, "Got:", got) - } -} - -func TestGenericGroup_withEmail(t *testing.T) { - t.Parallel() - - response := struct { - Email string `json:"email"` - }{ - "martymcfly@pinheads.rok", - } - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - rw.WriteHeader(http.StatusNotFound) - return - } - enc := json.NewEncoder(rw) - - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(response) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Generic{ - Logger: logger, - APIURL: mockAPI.URL, - APIKey: "email", - } - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - got, err := prov.Group(tc) - if err != nil { - t.Fatal("Unexpected error while retrieving PrincipalID: err:", err) - } - - want := "pinheads.rok" - if got != want { - t.Fatal("Retrieved group was not as expected. Want:", want, "Got:", got) - } -} - -func TestGenericPrincipalID(t *testing.T) { - t.Parallel() - - response := struct { - Email string `json:"email"` - }{ - "martymcfly@pinheads.rok", - } - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - rw.WriteHeader(http.StatusNotFound) - return - } - enc := json.NewEncoder(rw) - - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(response) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Generic{ - Logger: logger, - APIURL: mockAPI.URL, - APIKey: "email", - } - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - got, err := prov.PrincipalID(tc) - if err != nil { - t.Fatal("Unexpected error while retrieving PrincipalID: err:", err) - } - - want := "martymcfly@pinheads.rok" - if got != want { - t.Fatal("Retrieved email was not as expected. Want:", want, "Got:", got) - } -} - -func TestGenericPrincipalIDDomain(t *testing.T) { - t.Parallel() - expectedEmail := []struct { - Email string `json:"email"` - Primary bool `json:"primary"` - Verified bool `json:"verified"` - }{ - {"martymcfly@pinheads.rok", true, false}, - } - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" { - enc := json.NewEncoder(rw) - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(struct{}{}) - return - } - if r.URL.Path == "/emails" { - enc := json.NewEncoder(rw) - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(expectedEmail) - return - } - - rw.WriteHeader(http.StatusNotFound) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Generic{ - Logger: logger, - Domains: []string{"pinheads.rok"}, - } - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - got, err := prov.PrincipalID(tc) - if err != nil { - t.Fatal("Unexpected error while retrieving PrincipalID: err:", err) - } - want := "martymcfly@pinheads.rok" - if got != want { - t.Fatal("Retrieved email was not as expected. Want:", want, "Got:", got) - } -} diff --git a/chronograf/oauth2/github.go b/chronograf/oauth2/github.go deleted file mode 100644 index c06d554aa2a..00000000000 --- a/chronograf/oauth2/github.go +++ /dev/null @@ -1,198 +0,0 @@ -package oauth2 - -import ( - "context" - "crypto/rand" - "encoding/base64" - "errors" - "io" - "net/http" - "strings" - - "github.com/google/go-github/github" - "github.com/influxdata/influxdb/v2/chronograf" - "golang.org/x/oauth2" - ogh "golang.org/x/oauth2/github" -) - -var _ Provider = &Github{} - -// Github provides OAuth Login and Callback server. Callback will set -// an authentication cookie. This cookie's value is a JWT containing -// the user's primary Github email address. -type Github struct { - ClientID string - ClientSecret string - Orgs []string // Optional github organization checking - Logger chronograf.Logger -} - -// Name is the name of the provider. -func (g *Github) Name() string { - return "github" -} - -// ID returns the github application client id. -func (g *Github) ID() string { - return g.ClientID -} - -// Secret returns the github application client secret. -func (g *Github) Secret() string { - return g.ClientSecret -} - -// Scopes for github is only the email address and possible organizations if -// we are filtering by organizations. -func (g *Github) Scopes() []string { - scopes := []string{"user:email"} - // In order to access a users orgs, we need the "read:org" scope - // even if g.Orgs == 0 - scopes = append(scopes, "read:org") - return scopes -} - -// Config is the Github OAuth2 exchange information and endpoints. -func (g *Github) Config() *oauth2.Config { - return &oauth2.Config{ - ClientID: g.ID(), - ClientSecret: g.Secret(), - Scopes: g.Scopes(), - Endpoint: ogh.Endpoint, - } -} - -// PrincipalID returns the github email address of the user. -func (g *Github) PrincipalID(provider *http.Client) (string, error) { - client := github.NewClient(provider) - // If we need to restrict to a set of organizations, we first get the org - // and filter. - if len(g.Orgs) > 0 { - orgs, err := getOrganizations(client, g.Logger) - if err != nil { - return "", err - } - // Not a member, so, deny permission - if ok := isMember(g.Orgs, orgs); !ok { - g.Logger.Error("Not a member of required github organization") - return "", err - } - } - - email, err := getPrimaryEmail(client, g.Logger) - if err != nil { - return "", nil - } - return email, nil -} - -// Group returns a comma delimited string of Github organizations -// that a user belongs to in Github -func (g *Github) Group(provider *http.Client) (string, error) { - client := github.NewClient(provider) - orgs, err := getOrganizations(client, g.Logger) - if err != nil { - return "", err - } - - groups := []string{} - for _, org := range orgs { - if org.Login != nil { - groups = append(groups, *org.Login) - continue - } - } - - return strings.Join(groups, ","), nil -} - -func randomString(length int) string { - k := make([]byte, length) - if _, err := io.ReadFull(rand.Reader, k); err != nil { - return "" - } - return base64.StdEncoding.EncodeToString(k) -} - -func logResponseError(log chronograf.Logger, resp *github.Response, err error) { - switch resp.StatusCode { - case http.StatusUnauthorized, http.StatusForbidden: - log.Error("OAuth access to email address forbidden ", err.Error()) - default: - log.Error("Unable to retrieve Github email ", err.Error()) - } -} - -// isMember makes sure that the user is in one of the required organizations. -func isMember(requiredOrgs []string, userOrgs []*github.Organization) bool { - for _, requiredOrg := range requiredOrgs { - for _, userOrg := range userOrgs { - if userOrg.Login != nil && *userOrg.Login == requiredOrg { - return true - } - } - } - return false -} - -// getOrganizations gets all organization for the currently authenticated user. -func getOrganizations(client *github.Client, log chronograf.Logger) ([]*github.Organization, error) { - // Get all pages of results - var allOrgs []*github.Organization - for { - opt := &github.ListOptions{ - PerPage: 10, - } - // Get the organizations for the current authenticated user. - orgs, resp, err := client.Organizations.List(context.TODO(), "", opt) - if err != nil { - logResponseError(log, resp, err) - return nil, err - } - allOrgs = append(allOrgs, orgs...) - if resp.NextPage == 0 { - break - } - opt.Page = resp.NextPage - } - return allOrgs, nil -} - -// getPrimaryEmail gets the primary email account for the authenticated user. -func getPrimaryEmail(client *github.Client, log chronograf.Logger) (string, error) { - emails, resp, err := client.Users.ListEmails(context.TODO(), nil) - if err != nil { - logResponseError(log, resp, err) - return "", err - } - - email, err := primaryEmail(emails) - if err != nil { - log.Error("Unable to retrieve primary Github email ", err.Error()) - return "", err - } - return email, nil -} - -func primaryEmail(emails []*github.UserEmail) (string, error) { - for _, m := range emails { - if m != nil && getPrimary(m) && getVerified(m) && m.Email != nil { - return *m.Email, nil - } - } - return "", errors.New("no primary email address") -} - -func getPrimary(m *github.UserEmail) bool { - if m == nil || m.Primary == nil { - return false - } - return *m.Primary -} - -func getVerified(m *github.UserEmail) bool { - if m == nil || m.Verified == nil { - return false - } - return *m.Verified -} diff --git a/chronograf/oauth2/github_test.go b/chronograf/oauth2/github_test.go deleted file mode 100644 index ed988dcda60..00000000000 --- a/chronograf/oauth2/github_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package oauth2_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -func TestGithubPrincipalID(t *testing.T) { - t.Parallel() - - expected := []struct { - Email string `json:"email"` - Primary bool `json:"primary"` - Verified bool `json:"verified"` - }{ - {"mcfly@example.com", false, true}, - {"martymcspelledwrong@example.com", false, false}, - {"martymcfly@example.com", true, true}, - } - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/user/emails" { - rw.WriteHeader(http.StatusNotFound) - return - } - enc := json.NewEncoder(rw) - - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(expected) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Github{ - Logger: logger, - } - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - email, err := prov.PrincipalID(tc) - if err != nil { - t.Fatal("Unexpected error while retrieving PrincipalID: err:", err) - } - - if got, want := email, "martymcfly@example.com"; got != want { - t.Fatal("Retrieved email was not as expected. Want:", want, "Got:", got) - } -} - -func TestGithubPrincipalIDOrganization(t *testing.T) { - t.Parallel() - - expectedUser := []struct { - Email string `json:"email"` - Primary bool `json:"primary"` - Verified bool `json:"verified"` - }{ - {"martymcfly@example.com", true, true}, - } - expectedOrg := []struct { - Login string `json:"login"` - }{ - {"Hill Valley Preservation Society"}, - } - - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/user/emails" { - enc := json.NewEncoder(rw) - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(expectedUser) - return - } - if r.URL.Path == "/user/orgs" { - enc := json.NewEncoder(rw) - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(expectedOrg) - return - } - rw.WriteHeader(http.StatusNotFound) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Github{ - Logger: logger, - Orgs: []string{"Hill Valley Preservation Society"}, - } - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - email, err := prov.PrincipalID(tc) - if err != nil { - t.Fatal("Unexpected error while retrieving PrincipalID: err:", err) - } - - if email != expectedUser[0].Email { - t.Fatal("Retrieved email was not as expected. Want:", expectedUser[0].Email, "Got:", email) - } -} diff --git a/chronograf/oauth2/google.go b/chronograf/oauth2/google.go deleted file mode 100644 index e20029d9c1b..00000000000 --- a/chronograf/oauth2/google.go +++ /dev/null @@ -1,107 +0,0 @@ -package oauth2 - -import ( - "context" - "fmt" - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" - "golang.org/x/oauth2" - goauth2 "google.golang.org/api/oauth2/v2" - "google.golang.org/api/option" -) - -// GoogleEndpoint is Google's OAuth 2.0 endpoint. -// Copied here to remove tons of package dependencies -var GoogleEndpoint = oauth2.Endpoint{ - AuthURL: "https://accounts.google.com/o/oauth2/auth", - TokenURL: "https://accounts.google.com/o/oauth2/token", -} -var _ Provider = &Google{} - -// Google is an oauth2 provider supporting google. -type Google struct { - ClientID string - ClientSecret string - RedirectURL string - Domains []string // Optional google email domain checking - Logger chronograf.Logger -} - -// Name is the name of the provider -func (g *Google) Name() string { - return "google" -} - -// ID returns the google application client id -func (g *Google) ID() string { - return g.ClientID -} - -// Secret returns the google application client secret -func (g *Google) Secret() string { - return g.ClientSecret -} - -// Scopes for google is only the email address -// Documentation is here: https://developers.google.com/+/web/api/rest/oauth#email -func (g *Google) Scopes() []string { - return []string{ - goauth2.UserinfoEmailScope, - goauth2.UserinfoProfileScope, - } -} - -// Config is the Google OAuth2 exchange information and endpoints -func (g *Google) Config() *oauth2.Config { - return &oauth2.Config{ - ClientID: g.ID(), - ClientSecret: g.Secret(), - Scopes: g.Scopes(), - Endpoint: GoogleEndpoint, - RedirectURL: g.RedirectURL, - } -} - -// PrincipalID returns the google email address of the user. -func (g *Google) PrincipalID(provider *http.Client) (string, error) { - srv, err := goauth2.NewService(context.TODO(), option.WithHTTPClient(provider)) - if err != nil { - g.Logger.Error("Unable to communicate with Google ", err.Error()) - return "", err - } - info, err := srv.Userinfo.Get().Do() - if err != nil { - g.Logger.Error("Unable to retrieve Google email ", err.Error()) - return "", err - } - // No domain filtering required, so, the user is authenticated. - if len(g.Domains) == 0 { - return info.Email, nil - } - - // Check if the account domain is acceptable - for _, requiredDomain := range g.Domains { - if info.Hd == requiredDomain { - return info.Email, nil - } - } - g.Logger.Error("Domain '", info.Hd, "' is not a member of required Google domain(s): ", g.Domains) - return "", fmt.Errorf("not in required domain") -} - -// Group returns the string of domain a user belongs to in Google -func (g *Google) Group(provider *http.Client) (string, error) { - srv, err := goauth2.NewService(context.TODO(), option.WithHTTPClient(provider)) - if err != nil { - g.Logger.Error("Unable to communicate with Google ", err.Error()) - return "", err - } - info, err := srv.Userinfo.Get().Do() - if err != nil { - g.Logger.Error("Unable to retrieve Google email ", err.Error()) - return "", err - } - - return info.Hd, nil -} diff --git a/chronograf/oauth2/google_test.go b/chronograf/oauth2/google_test.go deleted file mode 100644 index ed99d1e0ce5..00000000000 --- a/chronograf/oauth2/google_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package oauth2_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -func TestGooglePrincipalID(t *testing.T) { - t.Parallel() - - expected := struct { - Email string `json:"email"` - }{ - "martymcfly@example.com", - } - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/oauth2/v2/userinfo" { - rw.WriteHeader(http.StatusNotFound) - return - } - - enc := json.NewEncoder(rw) - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(expected) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Google{ - Logger: logger, - } - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - email, err := prov.PrincipalID(tc) - if err != nil { - t.Fatal("Unexpected error while retrieving PrincipalID: err:", err) - } - - if email != expected.Email { - t.Fatal("Retrieved email was not as expected. Want:", expected.Email, "Got:", email) - } -} - -func TestGooglePrincipalIDDomain(t *testing.T) { - t.Parallel() - - expectedUser := struct { - Email string `json:"email"` - Hd string `json:"hd"` - }{ - "martymcfly@example.com", - "Hill Valley Preservation Society", - } - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/oauth2/v2/userinfo" { - rw.WriteHeader(http.StatusNotFound) - return - } - - enc := json.NewEncoder(rw) - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(expectedUser) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Google{ - Logger: logger, - Domains: []string{"Hill Valley Preservation Society"}, - } - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - email, err := prov.PrincipalID(tc) - if err != nil { - t.Fatal("Unexpected error while retrieving PrincipalID: err:", err) - } - - if email != expectedUser.Email { - t.Fatal("Retrieved email was not as expected. Want:", expectedUser.Email, "Got:", email) - } -} diff --git a/chronograf/oauth2/heroku.go b/chronograf/oauth2/heroku.go deleted file mode 100644 index 2cf036db1da..00000000000 --- a/chronograf/oauth2/heroku.go +++ /dev/null @@ -1,145 +0,0 @@ -package oauth2 - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" - "golang.org/x/oauth2" - hrk "golang.org/x/oauth2/heroku" -) - -// Ensure that Heroku is an oauth2.Provider -var _ Provider = &Heroku{} - -const ( - // HerokuAccountRoute is required for interacting with Heroku API - HerokuAccountRoute string = "https://api.heroku.com/account" -) - -// Heroku is an OAuth2 Provider allowing users to authenticate with Heroku to -// gain access to Chronograf -type Heroku struct { - // OAuth2 Secrets - ClientID string - ClientSecret string - - Organizations []string // set of organizations permitted to access the protected resource. Empty means "all" - - Logger chronograf.Logger -} - -// Config returns the OAuth2 exchange information and endpoints -func (h *Heroku) Config() *oauth2.Config { - return &oauth2.Config{ - ClientID: h.ID(), - ClientSecret: h.Secret(), - Scopes: h.Scopes(), - Endpoint: hrk.Endpoint, - } -} - -// ID returns the Heroku application client ID -func (h *Heroku) ID() string { - return h.ClientID -} - -// Name returns the name of this provider (heroku) -func (h *Heroku) Name() string { - return "heroku" -} - -// PrincipalID returns the Heroku email address of the user. -func (h *Heroku) PrincipalID(provider *http.Client) (string, error) { - type DefaultOrg struct { - ID string `json:"id"` - Name string `json:"name"` - } - type Account struct { - Email string `json:"email"` - DefaultOrganization DefaultOrg `json:"default_organization"` - } - - req, err := http.NewRequest("GET", HerokuAccountRoute, nil) - if err != nil { - return "", err - } - - // Requests fail to Heroku unless this Accept header is set. - req.Header.Set("Accept", "application/vnd.heroku+json; version=3") - resp, err := provider.Do(req) - if resp.StatusCode/100 != 2 { - err := fmt.Errorf( - "unable to GET user data from %s. Status: %s", - HerokuAccountRoute, - resp.Status, - ) - h.Logger.Error("", err) - return "", err - } - if err != nil { - h.Logger.Error("Unable to communicate with Heroku. err:", err) - return "", err - } - defer resp.Body.Close() - d := json.NewDecoder(resp.Body) - - var account Account - if err := d.Decode(&account); err != nil { - h.Logger.Error("Unable to decode response from Heroku. err:", err) - return "", err - } - - // check if member of org - if len(h.Organizations) > 0 { - for _, org := range h.Organizations { - if account.DefaultOrganization.Name == org { - return account.Email, nil - } - } - h.Logger.Error(ErrOrgMembership) - return "", ErrOrgMembership - } - return account.Email, nil -} - -// Group returns the Heroku organization that user belongs to. -func (h *Heroku) Group(provider *http.Client) (string, error) { - type DefaultOrg struct { - ID string `json:"id"` - Name string `json:"name"` - } - type Account struct { - Email string `json:"email"` - DefaultOrganization DefaultOrg `json:"default_organization"` - } - - resp, err := provider.Get(HerokuAccountRoute) - if err != nil { - h.Logger.Error("Unable to communicate with Heroku. err:", err) - return "", err - } - defer resp.Body.Close() - d := json.NewDecoder(resp.Body) - - var account Account - if err := d.Decode(&account); err != nil { - h.Logger.Error("Unable to decode response from Heroku. err:", err) - return "", err - } - - return account.DefaultOrganization.Name, nil -} - -// Scopes for heroku is "identity" which grants access to user account -// information. This will grant us access to the user's email address which is -// used as the Principal's identifier. -func (h *Heroku) Scopes() []string { - return []string{"identity"} -} - -// Secret returns the Heroku application client secret -func (h *Heroku) Secret() string { - return h.ClientSecret -} diff --git a/chronograf/oauth2/heroku_test.go b/chronograf/oauth2/heroku_test.go deleted file mode 100644 index e6785b55599..00000000000 --- a/chronograf/oauth2/heroku_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package oauth2_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -func Test_Heroku_PrincipalID_ExtractsEmailAddress(t *testing.T) { - t.Parallel() - - expected := struct { - Email string `json:"email"` - }{ - "martymcfly@example.com", - } - - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/account" { - rw.WriteHeader(http.StatusNotFound) - return - } - enc := json.NewEncoder(rw) - - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(expected) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Heroku{ - Logger: logger, - } - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - email, err := prov.PrincipalID(tc) - if err != nil { - t.Fatal("Unexpected error while retrieving PrincipalID: err:", err) - } - - if email != expected.Email { - t.Fatal("Retrieved email was not as expected. Want:", expected.Email, "Got:", email) - } -} - -func Test_Heroku_PrincipalID_RestrictsByOrganization(t *testing.T) { - t.Parallel() - - expected := struct { - Email string `json:"email"` - DefaultOrganization map[string]string `json:"default_organization"` - }{ - "martymcfly@example.com", - map[string]string{ - "id": "a85eac89-56cc-498e-9a89-d8f49f6aed71", - "name": "hill-valley-preservation-society", - }, - } - - mockAPI := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/account" { - rw.WriteHeader(http.StatusNotFound) - return - } - enc := json.NewEncoder(rw) - - rw.WriteHeader(http.StatusOK) - _ = enc.Encode(expected) - })) - defer mockAPI.Close() - - logger := &chronograf.NoopLogger{} - prov := oauth2.Heroku{ - Logger: logger, - Organizations: []string{"enchantment-under-the-sea-dance-committee"}, - } - - tt, err := oauth2.NewTestTripper(logger, mockAPI, http.DefaultTransport) - if err != nil { - t.Fatal("Error initializing TestTripper: err:", err) - } - - tc := &http.Client{ - Transport: tt, - } - - _, err = prov.PrincipalID(tc) - if err == nil { - t.Fatal("Expected error while authenticating user with mismatched orgs, but received none") - } -} diff --git a/chronograf/oauth2/jwt.go b/chronograf/oauth2/jwt.go deleted file mode 100644 index c5f51eee3a8..00000000000 --- a/chronograf/oauth2/jwt.go +++ /dev/null @@ -1,262 +0,0 @@ -package oauth2 - -import ( - "context" - "crypto/x509" - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "time" - - gojwt "github.com/dgrijalva/jwt-go" -) - -// Ensure JWT conforms to the Tokenizer interface -var _ Tokenizer = &JWT{} - -// JWT represents a javascript web token that can be validated or marshaled into string. -type JWT struct { - Secret string - Jwksurl string - Now func() time.Time -} - -// NewJWT creates a new JWT using time.Now -// secret is used for signing and validating signatures (HS256/HMAC) -// jwksurl is used for validating RS256 signatures. -func NewJWT(secret string, jwksurl string) *JWT { - return &JWT{ - Secret: secret, - Jwksurl: jwksurl, - Now: DefaultNowTime, - } -} - -// Ensure Claims implements the jwt.Claims interface -var _ gojwt.Claims = &Claims{} - -// Claims extends jwt.StandardClaims' Valid to make sure claims has a subject. -type Claims struct { - gojwt.StandardClaims - // We were unable to find a standard claim at https://www.iana.org/assignments/jwt/jwt.xhtml - // that felt appropriate for Organization. As a result, we added a custom `org` field. - Organization string `json:"org,omitempty"` - // We were unable to find a standard claim at https://www.iana.org/assignments/jwt/jwt.xhtml - // that felt appropriate for a users Group(s). As a result we added a custom `grp` field. - // Multiple groups may be specified by comma delimiting the various group. - // - // The singlular `grp` was chosen over the `grps` to keep consistent with the JWT naming - // convention (it is common for singlularly named values to actually be arrays, see `given_name`, - // `family_name`, and `middle_name` in the iana link provided above). I should add the discalimer - // I'm currently sick, so this thought process might be off. - Group string `json:"grp,omitempty"` -} - -// Valid adds an empty subject test to the StandardClaims checks. -func (c *Claims) Valid() error { - if err := c.StandardClaims.Valid(); err != nil { - return err - } else if c.StandardClaims.Subject == "" { - return fmt.Errorf("claim has no subject") - } - - return nil -} - -// ValidPrincipal checks if the jwtToken is signed correctly and validates with Claims. lifespan is the -// maximum valid lifetime of a token. If the lifespan is 0 then the auth lifespan duration is not checked. -func (j *JWT) ValidPrincipal(ctx context.Context, jwtToken Token, lifespan time.Duration) (Principal, error) { - gojwt.TimeFunc = j.Now - - // Check for expected signing method. - alg := j.KeyFunc - - return j.ValidClaims(jwtToken, lifespan, alg) -} - -// KeyFunc verifies HMAC or RSA/RS256 signatures -func (j *JWT) KeyFunc(token *gojwt.Token) (interface{}, error) { - if _, ok := token.Method.(*gojwt.SigningMethodHMAC); ok { - return []byte(j.Secret), nil - } else if _, ok := token.Method.(*gojwt.SigningMethodRSA); ok { - return j.KeyFuncRS256(token) - } - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) -} - -// For the id_token, the recommended signature algorithm is RS256, which -// means we need to verify the token against a public key. This public key -// is available from the key discovery service in JSON Web Key (JWK). -// JWK is specified in RFC 7517. -// -// The location of the key discovery service (JWKSURL) is published in the -// OpenID Provider Configuration Information at /.well-known/openid-configuration -// implements rfc7517 section 4.7 "x5c" (X.509 Certificate Chain) Parameter - -// JWK defines a JSON Web KEy nested struct -type JWK struct { - Kty string `json:"kty"` - Use string `json:"use"` - Alg string `json:"alg"` - Kid string `json:"kid"` - X5t string `json:"x5t"` - N string `json:"n"` - E string `json:"e"` - X5c []string `json:"x5c"` -} - -// JWKS defines a JKW[] -type JWKS struct { - Keys []JWK `json:"keys"` -} - -// KeyFuncRS256 verifies RS256 signed JWT tokens, it looks up the signing key in the key discovery service -func (j *JWT) KeyFuncRS256(token *gojwt.Token) (interface{}, error) { - // Don't forget to validate the alg is what you expect: - if _, ok := token.Method.(*gojwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf("unsupported signing method: %v", token.Header["alg"]) - } - - // read JWKS document from key discovery service - if j.Jwksurl == "" { - return nil, fmt.Errorf("token JWKSURL not specified, cannot validate RS256 signature") - } - - rr, err := http.Get(j.Jwksurl) - if err != nil { - return nil, err - } - defer rr.Body.Close() - body, err := ioutil.ReadAll(rr.Body) - if err != nil { - return nil, err - } - - // parse json to struct - var jwks JWKS - if err := json.Unmarshal([]byte(body), &jwks); err != nil { - return nil, err - } - - // extract cert when kid and alg match - var certPkix []byte - for _, jwk := range jwks.Keys { - if token.Header["kid"] == jwk.Kid { - // FIXME: optionally walk the key chain, see rfc7517 section 4.7 - certPkix, err = base64.StdEncoding.DecodeString(jwk.X5c[0]) - if err != nil { - return nil, fmt.Errorf("base64 decode error for JWK kid %v", token.Header["kid"]) - } - } - } - if certPkix == nil { - return nil, fmt.Errorf("no signing key found for kid %v", token.Header["kid"]) - } - - // parse certificate (from PKIX format) and return signing key - cert, err := x509.ParseCertificate(certPkix) - if err != nil { - return nil, err - } - return cert.PublicKey, nil -} - -// ValidClaims validates a token with StandardClaims -func (j *JWT) ValidClaims(jwtToken Token, lifespan time.Duration, alg gojwt.Keyfunc) (Principal, error) { - // 1. Checks for expired tokens - // 2. Checks if time is after the issued at - // 3. Check if time is after not before (nbf) - // 4. Check if subject is not empty - // 5. Check if duration less than auth lifespan - token, err := gojwt.ParseWithClaims(string(jwtToken), &Claims{}, alg) - if err != nil { - return Principal{}, err - // at time of this writing and researching the docs, token.Valid seems to be always true - } else if !token.Valid { - return Principal{}, err - } - - // at time of this writing and researching the docs, there will always be claims - claims, ok := token.Claims.(*Claims) - if !ok { - return Principal{}, fmt.Errorf("unable to convert claims to standard claims") - } - - exp := time.Unix(claims.ExpiresAt, 0) - iat := time.Unix(claims.IssuedAt, 0) - - // If the duration of the claim is longer than the auth lifespan then this is - // an invalid claim because server assumes that lifespan is the maximum possible - // duration. However, a lifespan of zero means that the duration comparison - // against the auth duration is not needed. - if lifespan > 0 && exp.Sub(iat) > lifespan { - return Principal{}, fmt.Errorf("claims duration is different from auth lifespan") - } - - return Principal{ - Subject: claims.Subject, - Issuer: claims.Issuer, - Organization: claims.Organization, - Group: claims.Group, - ExpiresAt: exp, - IssuedAt: iat, - }, nil -} - -// GetClaims extracts claims from id_token -func (j *JWT) GetClaims(tokenString string) (gojwt.MapClaims, error) { - var claims gojwt.MapClaims - - gojwt.TimeFunc = j.Now - token, err := gojwt.Parse(tokenString, j.KeyFunc) - if err != nil { - return nil, err - } - - if !token.Valid { - return nil, fmt.Errorf("token is not valid") - } - - claims, ok := token.Claims.(gojwt.MapClaims) - if !ok { - return nil, fmt.Errorf("token has no claims") - } - - return claims, nil -} - -// Create creates a signed JWT token from user that expires at Principal's ExpireAt time. -func (j *JWT) Create(ctx context.Context, user Principal) (Token, error) { - // Create a new token object, specifying signing method and the claims - // you would like it to contain. - claims := &Claims{ - StandardClaims: gojwt.StandardClaims{ - Subject: user.Subject, - Issuer: user.Issuer, - ExpiresAt: user.ExpiresAt.Unix(), - IssuedAt: user.IssuedAt.Unix(), - NotBefore: user.IssuedAt.Unix(), - }, - Organization: user.Organization, - Group: user.Group, - } - token := gojwt.NewWithClaims(gojwt.SigningMethodHS256, claims) - // Sign and get the complete encoded token as a string using the secret - t, err := token.SignedString([]byte(j.Secret)) - // this will only fail if the JSON can't be encoded correctly - if err != nil { - return "", err - } - return Token(t), nil -} - -// ExtendedPrincipal sets the expires at to be the current time plus the extention into the future -func (j *JWT) ExtendedPrincipal(ctx context.Context, principal Principal, extension time.Duration) (Principal, error) { - // Extend the time of expiration. Do not change IssuedAt as the - // lifetime of the token is extended, but, NOT the original time - // of issue. This is used to enforce a maximum lifetime of a token - principal.ExpiresAt = j.Now().Add(extension) - return principal, nil -} diff --git a/chronograf/oauth2/jwt_test.go b/chronograf/oauth2/jwt_test.go deleted file mode 100644 index 904429135cd..00000000000 --- a/chronograf/oauth2/jwt_test.go +++ /dev/null @@ -1,288 +0,0 @@ -package oauth2_test - -import ( - "context" - "errors" - "io" - "net/http" - "net/http/httptest" - "reflect" - "testing" - "time" - - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -func TestAuthenticate(t *testing.T) { - history := time.Unix(-446774400, 0) - var tests = []struct { - Desc string - Secret string - // JWT tokens were generated at https://jwt.io/ using their Debugger - Token oauth2.Token - Duration time.Duration - Principal oauth2.Principal - Err error - }{ - { - Desc: "Test bad jwt token", - Secret: "secret", - Token: "badtoken", - Principal: oauth2.Principal{ - Subject: "", - }, - Err: errors.New("token contains an invalid number of segments"), - }, - { - Desc: "Test valid jwt token", - Secret: "secret", - Token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIvY2hyb25vZ3JhZi92MS91c2Vycy8xIiwibmFtZSI6IkRvYyBCcm93biIsImlhdCI6LTQ0Njc3NDQwMCwiZXhwIjotNDQ2Nzc0Mzk5LCJuYmYiOi00NDY3NzQ0MDB9.Ga0zGXWTT2CBVnnIhIO5tUAuBEVk4bKPaT4t4MU1ngo", - Duration: time.Second, - Principal: oauth2.Principal{ - Subject: "/chronograf/v1/users/1", - ExpiresAt: history.Add(time.Second), - IssuedAt: history, - }, - }, - { - Desc: "Test valid jwt token with organization", - Secret: "secret", - Token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIvY2hyb25vZ3JhZi92MS91c2Vycy8xIiwibmFtZSI6IkRvYyBCcm93biIsIm9yZyI6IjEzMzciLCJpYXQiOi00NDY3NzQ0MDAsImV4cCI6LTQ0Njc3NDM5OSwibmJmIjotNDQ2Nzc0NDAwfQ.b38MK5liimWsvvJr4a3GNYRDJOAN7WCrfZ0FfZftqjc", - Duration: time.Second, - Principal: oauth2.Principal{ - Subject: "/chronograf/v1/users/1", - Organization: "1337", - ExpiresAt: history.Add(time.Second), - IssuedAt: history, - }, - }, - { - Desc: "Test expired jwt token", - Secret: "secret", - Token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIvY2hyb25vZ3JhZi92MS91c2Vycy8xIiwibmFtZSI6IkRvYyBCcm93biIsImlhdCI6LTQ0Njc3NDQwMCwiZXhwIjotNDQ2Nzc0NDAxLCJuYmYiOi00NDY3NzQ0MDB9.vWXdm0-XQ_pW62yBpSISFFJN_yz0vqT9_INcUKTp5Q8", - Duration: time.Second, - Principal: oauth2.Principal{ - Subject: "", - ExpiresAt: history.Add(time.Second), - IssuedAt: history, - }, - Err: errors.New("token is expired by 1s"), - }, - { - Desc: "Test jwt token not before time", - Secret: "secret", - Token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIvY2hyb25vZ3JhZi92MS91c2Vycy8xIiwibmFtZSI6IkRvYyBCcm93biIsImlhdCI6LTQ0Njc3NDQwMCwiZXhwIjotNDQ2Nzc0NDAwLCJuYmYiOi00NDY3NzQzOTl9.TMGAhv57u1aosjc4ywKC7cElP1tKyQH7GmRF2ToAxlE", - Duration: time.Second, - Principal: oauth2.Principal{ - Subject: "", - ExpiresAt: history.Add(time.Second), - IssuedAt: history, - }, - Err: errors.New("token is not valid yet"), - }, - { - Desc: "Test jwt with empty subject is invalid", - Secret: "secret", - Token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOi00NDY3NzQ0MDAsImV4cCI6LTQ0Njc3NDQwMCwibmJmIjotNDQ2Nzc0NDAwfQ.gxsA6_Ei3s0f2I1TAtrrb8FmGiO25OqVlktlF_ylhX4", - Duration: time.Second, - Principal: oauth2.Principal{ - Subject: "", - ExpiresAt: history.Add(time.Second), - IssuedAt: history, - }, - Err: errors.New("claim has no subject"), - }, - { - Desc: "Test jwt duration matches auth duration", - Secret: "secret", - Token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOi00NDY3NzQzMDAsImlhdCI6LTQ0Njc3NDQwMCwiaXNzIjoiaGlsbHZhbGxleSIsIm5iZiI6LTQ0Njc3NDQwMCwic3ViIjoibWFydHlAcGluaGVhZC5uZXQifQ.njEjstpuIDnghSR7VyPPB9QlvJ6Q5JpR3ZEZ_8vGYfA", - Duration: time.Second, - Principal: oauth2.Principal{ - Subject: "marty@pinhead.net", - ExpiresAt: history, - IssuedAt: history.Add(100 * time.Second), - }, - Err: errors.New("claims duration is different from auth lifespan"), - }, - } - for _, test := range tests { - j := oauth2.JWT{ - Secret: test.Secret, - Now: func() time.Time { - return time.Unix(-446774400, 0) - }, - } - principal, err := j.ValidPrincipal(context.Background(), test.Token, test.Duration) - if test.Err != nil && err == nil { - t.Fatalf("Expected err %s", test.Err.Error()) - } - if err != nil { - if test.Err == nil { - t.Errorf("Error in test %s authenticating with bad token: %v", test.Desc, err) - } else if err.Error() != test.Err.Error() { - t.Errorf("Error in test %s expected error: %v actual: %v", test.Desc, test.Err, err) - } - } else if test.Principal != principal { - t.Errorf("Error in test %s; principals different; expected: %v actual: %v", test.Desc, test.Principal, principal) - } - } - -} - -func TestToken(t *testing.T) { - expected := oauth2.Token("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOi00NDY3NzQzOTksImlhdCI6LTQ0Njc3NDQwMCwibmJmIjotNDQ2Nzc0NDAwLCJzdWIiOiIvY2hyb25vZ3JhZi92MS91c2Vycy8xIn0.ofQM6yTmrmve5JeEE0RcK4_euLXuZ_rdh6bLAbtbC9M") - history := time.Unix(-446774400, 0) - j := oauth2.JWT{ - Secret: "secret", - Now: func() time.Time { - return history - }, - } - p := oauth2.Principal{ - Subject: "/chronograf/v1/users/1", - ExpiresAt: history.Add(time.Second), - IssuedAt: history, - } - if token, err := j.Create(context.Background(), p); err != nil { - t.Errorf("Error creating token for principal: %v", err) - } else if token != expected { - t.Errorf("Error creating token; expected: %s actual: %s", expected, token) - } -} - -func TestSigningMethod(t *testing.T) { - token := oauth2.Token("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.EkN-DOsnsuRjRO6BxXemmJDm3HbxrbRzXglbN2S4sOkopdU4IsDxTI8jO19W_A4K8ZPJijNLis4EZsHeY559a4DFOd50_OqgHGuERTqYZyuhtF39yxJPAjUESwxk2J5k_4zM3O-vtd1Ghyo4IbqKKSy6J9mTniYJPenn5-HIirE") - j := oauth2.JWT{} - if _, err := j.ValidPrincipal(context.Background(), token, 0); err == nil { - t.Error("Error was expected while validating incorrectly signed token") - } else if err.Error() != "token JWKSURL not specified, cannot validate RS256 signature" { - t.Errorf("Error wanted 'token JWKSURL not specified, cannot validate RS256 signature', got %s", err.Error()) - } -} - -func TestGetClaims(t *testing.T) { - var tests = []struct { - Name string - TokenString string - JwksDocument string - Iat int64 - Err error - }{ - { - Name: "Valid Token with RS256 signature verified against correct JWKS document", - TokenString: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSIsImtpZCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSJ9.eyJhdWQiOiJjaHJvbm9ncmFmIiwiaXNzIjoiaHR0cHM6Ly9kc3RjaW1hYWQxcC5kc3QtaXRzLmRlL2FkZnMiLCJpYXQiOjE1MTMxNjU4ODksImV4cCI6MTUxMzE2OTQ4OSwiYXV0aF90aW1lIjoxNTEzMTY1ODg4LCJzdWIiOiJlWVYzamRsZE55RlkxcUZGSDRvQWRCdkRGZmJWZm51RzI5SGlIa1N1andrPSIsInVwbiI6ImJzY0Bkc3QtaXRzLmRlIiwidW5pcXVlX25hbWUiOiJEU1RcXGJzYyIsInNpZCI6IlMtMS01LTIxLTI1MDUxNTEzOTgtMjY2MTAyODEwOS0zNzU0MjY1ODIwLTExMDQifQ.nK51Ui4XN45SVul9igNaKFQd-F63BNstBzW-T5LBVm_ANHCEHyP3_88C3ffkkQIi3PxYacRJGtfswP35ws7YJUcNp-GoGZARqz62NpMtbQyhos6mCaVXwPoxPbrZx4AkMQgxkZwJcOzceX7mpjcT3kCth30chN3lkhzSjGrXe4ZDOAV25liS-dsdBiqDiaTB91sS534GM76qJQxFUs51oSbYTRdCN1VJ0XopMcasfVDzFrtSbyvEIVXlpKK2HplnhheqF4QHrM_3cjV_NGRr3tYLe-AGTdDXKWlJD1GDz1ECXeMGQHPoz3U8cqNsFLYBstIlCgfnBWgWsPZSvJPJUg", - JwksDocument: `{"keys":[{"kty":"RSA","use":"sig","alg":"RS256","kid":"YDBUhqdWksKXdGuX0sytjaUuxhA","x5t":"YDBUhqdWksKXdGuX0sytjaUuxhA","n":"uwVVrs5OJRKeLUk0H5N_b4Jbvff3rxlg3WIeOO-zSSPTC5oFOc5_te0rLgVoNJJB4rNM4A7BEXI885xLrjfL3l3LHqaJetvR0tdLAnkvbUKUiGxnuGnmOsgh491P95pHPIAniy2p64FQoBbTJ0a6cF5LRuPPHKVXgjXjTydvmKrt_IVaWUDgICRsw5Bbv290SahmxcdO3akSgfsZtRkR8SmaMzAPYINi2_8P2evaKAnMQLTgUVkctaEamO_6HJ5f5sWheV7trLekU35xPVkPwShDelefnhyJcO5yICXqXzuewBEni9LrxAEJYN2rYfiFQWJy-pDe5DPUBs-IFTpctQ","e":"AQAB","x5c":["MIIC6DCCAdCgAwIBAgIQPszqLhbrpZlE+jEJTyJg7jANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVBREZTIFNpZ25pbmcgLSBkc3RjaW1hYWQxcC5kc3QtaXRzLmRlMB4XDTE3MTIwNDE0MDEwOFoXDTE4MTIwNDE0MDEwOFowMDEuMCwGA1UEAxMlQURGUyBTaWduaW5nIC0gZHN0Y2ltYWFkMXAuZHN0LWl0cy5kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALsFVa7OTiUSni1JNB+Tf2+CW733968ZYN1iHjjvs0kj0wuaBTnOf7XtKy4FaDSSQeKzTOAOwRFyPPOcS643y95dyx6miXrb0dLXSwJ5L21ClIhsZ7hp5jrIIePdT\/eaRzyAJ4stqeuBUKAW0ydGunBeS0bjzxylV4I1408nb5iq7fyFWllA4CAkbMOQW79vdEmoZsXHTt2pEoH7GbUZEfEpmjMwD2CDYtv\/D9nr2igJzEC04FFZHLWhGpjv+hyeX+bFoXle7ay3pFN+cT1ZD8EoQ3pXn54ciXDuciAl6l87nsARJ4vS68QBCWDdq2H4hUFicvqQ3uQz1AbPiBU6XLUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAPHCisZyPf\/fuuQEW5LyzZSYMwBRYVR6kk\/M2ZNx6TrUEwmOb10RQ3G97bLAshN44g5lWdPYz4EOt6d2o71etIjf79f+IR0MAjEgBB2HThaHcMU9KG229Ftcauie9XeurngMawTRu60YqH7+go8EMf6a1Kdnx37DMy\/1LRlsYJVfEoOCab3GgcIdXrRSYWqsY4SVJZiTPYdqz9vmNPSXXiDSOTl6qXHV\/f53WTS2V5aIQbuJJziXlceusuVNny0o5h+j6ovZ1HhEGAu3lpD+8kY8KUqA4kXMH3VNZqzHBYazJx\/QBB3bG45cZSOvV3gUOnGBgiv9NBWjhvmY0fC3J6Q=="]}]}`, - Iat: int64(1513165889), - }, - { - Name: "Valid Token with RS256 signature verified against correct JWKS document but predated", - TokenString: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSIsImtpZCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSJ9.eyJhdWQiOiJjaHJvbm9ncmFmIiwiaXNzIjoiaHR0cHM6Ly9kc3RjaW1hYWQxcC5kc3QtaXRzLmRlL2FkZnMiLCJpYXQiOjE1MTMxNjU4ODksImV4cCI6MTUxMzE2OTQ4OSwiYXV0aF90aW1lIjoxNTEzMTY1ODg4LCJzdWIiOiJlWVYzamRsZE55RlkxcUZGSDRvQWRCdkRGZmJWZm51RzI5SGlIa1N1andrPSIsInVwbiI6ImJzY0Bkc3QtaXRzLmRlIiwidW5pcXVlX25hbWUiOiJEU1RcXGJzYyIsInNpZCI6IlMtMS01LTIxLTI1MDUxNTEzOTgtMjY2MTAyODEwOS0zNzU0MjY1ODIwLTExMDQifQ.nK51Ui4XN45SVul9igNaKFQd-F63BNstBzW-T5LBVm_ANHCEHyP3_88C3ffkkQIi3PxYacRJGtfswP35ws7YJUcNp-GoGZARqz62NpMtbQyhos6mCaVXwPoxPbrZx4AkMQgxkZwJcOzceX7mpjcT3kCth30chN3lkhzSjGrXe4ZDOAV25liS-dsdBiqDiaTB91sS534GM76qJQxFUs51oSbYTRdCN1VJ0XopMcasfVDzFrtSbyvEIVXlpKK2HplnhheqF4QHrM_3cjV_NGRr3tYLe-AGTdDXKWlJD1GDz1ECXeMGQHPoz3U8cqNsFLYBstIlCgfnBWgWsPZSvJPJUg", - JwksDocument: `{"keys":[{"kty":"RSA","use":"sig","alg":"RS256","kid":"YDBUhqdWksKXdGuX0sytjaUuxhA","x5t":"YDBUhqdWksKXdGuX0sytjaUuxhA","n":"uwVVrs5OJRKeLUk0H5N_b4Jbvff3rxlg3WIeOO-zSSPTC5oFOc5_te0rLgVoNJJB4rNM4A7BEXI885xLrjfL3l3LHqaJetvR0tdLAnkvbUKUiGxnuGnmOsgh491P95pHPIAniy2p64FQoBbTJ0a6cF5LRuPPHKVXgjXjTydvmKrt_IVaWUDgICRsw5Bbv290SahmxcdO3akSgfsZtRkR8SmaMzAPYINi2_8P2evaKAnMQLTgUVkctaEamO_6HJ5f5sWheV7trLekU35xPVkPwShDelefnhyJcO5yICXqXzuewBEni9LrxAEJYN2rYfiFQWJy-pDe5DPUBs-IFTpctQ","e":"AQAB","x5c":["MIIC6DCCAdCgAwIBAgIQPszqLhbrpZlE+jEJTyJg7jANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVBREZTIFNpZ25pbmcgLSBkc3RjaW1hYWQxcC5kc3QtaXRzLmRlMB4XDTE3MTIwNDE0MDEwOFoXDTE4MTIwNDE0MDEwOFowMDEuMCwGA1UEAxMlQURGUyBTaWduaW5nIC0gZHN0Y2ltYWFkMXAuZHN0LWl0cy5kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALsFVa7OTiUSni1JNB+Tf2+CW733968ZYN1iHjjvs0kj0wuaBTnOf7XtKy4FaDSSQeKzTOAOwRFyPPOcS643y95dyx6miXrb0dLXSwJ5L21ClIhsZ7hp5jrIIePdT\/eaRzyAJ4stqeuBUKAW0ydGunBeS0bjzxylV4I1408nb5iq7fyFWllA4CAkbMOQW79vdEmoZsXHTt2pEoH7GbUZEfEpmjMwD2CDYtv\/D9nr2igJzEC04FFZHLWhGpjv+hyeX+bFoXle7ay3pFN+cT1ZD8EoQ3pXn54ciXDuciAl6l87nsARJ4vS68QBCWDdq2H4hUFicvqQ3uQz1AbPiBU6XLUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAPHCisZyPf\/fuuQEW5LyzZSYMwBRYVR6kk\/M2ZNx6TrUEwmOb10RQ3G97bLAshN44g5lWdPYz4EOt6d2o71etIjf79f+IR0MAjEgBB2HThaHcMU9KG229Ftcauie9XeurngMawTRu60YqH7+go8EMf6a1Kdnx37DMy\/1LRlsYJVfEoOCab3GgcIdXrRSYWqsY4SVJZiTPYdqz9vmNPSXXiDSOTl6qXHV\/f53WTS2V5aIQbuJJziXlceusuVNny0o5h+j6ovZ1HhEGAu3lpD+8kY8KUqA4kXMH3VNZqzHBYazJx\/QBB3bG45cZSOvV3gUOnGBgiv9NBWjhvmY0fC3J6Q=="]}]}`, - Iat: int64(1513165889) - 1, - Err: errors.New("Token used before issued"), - }, - { - Name: "Valid Token with RS256 signature verified against correct JWKS document but outdated", - TokenString: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSIsImtpZCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSJ9.eyJhdWQiOiJjaHJvbm9ncmFmIiwiaXNzIjoiaHR0cHM6Ly9kc3RjaW1hYWQxcC5kc3QtaXRzLmRlL2FkZnMiLCJpYXQiOjE1MTMxNjU4ODksImV4cCI6MTUxMzE2OTQ4OSwiYXV0aF90aW1lIjoxNTEzMTY1ODg4LCJzdWIiOiJlWVYzamRsZE55RlkxcUZGSDRvQWRCdkRGZmJWZm51RzI5SGlIa1N1andrPSIsInVwbiI6ImJzY0Bkc3QtaXRzLmRlIiwidW5pcXVlX25hbWUiOiJEU1RcXGJzYyIsInNpZCI6IlMtMS01LTIxLTI1MDUxNTEzOTgtMjY2MTAyODEwOS0zNzU0MjY1ODIwLTExMDQifQ.nK51Ui4XN45SVul9igNaKFQd-F63BNstBzW-T5LBVm_ANHCEHyP3_88C3ffkkQIi3PxYacRJGtfswP35ws7YJUcNp-GoGZARqz62NpMtbQyhos6mCaVXwPoxPbrZx4AkMQgxkZwJcOzceX7mpjcT3kCth30chN3lkhzSjGrXe4ZDOAV25liS-dsdBiqDiaTB91sS534GM76qJQxFUs51oSbYTRdCN1VJ0XopMcasfVDzFrtSbyvEIVXlpKK2HplnhheqF4QHrM_3cjV_NGRr3tYLe-AGTdDXKWlJD1GDz1ECXeMGQHPoz3U8cqNsFLYBstIlCgfnBWgWsPZSvJPJUg", - JwksDocument: `{"keys":[{"kty":"RSA","use":"sig","alg":"RS256","kid":"YDBUhqdWksKXdGuX0sytjaUuxhA","x5t":"YDBUhqdWksKXdGuX0sytjaUuxhA","n":"uwVVrs5OJRKeLUk0H5N_b4Jbvff3rxlg3WIeOO-zSSPTC5oFOc5_te0rLgVoNJJB4rNM4A7BEXI885xLrjfL3l3LHqaJetvR0tdLAnkvbUKUiGxnuGnmOsgh491P95pHPIAniy2p64FQoBbTJ0a6cF5LRuPPHKVXgjXjTydvmKrt_IVaWUDgICRsw5Bbv290SahmxcdO3akSgfsZtRkR8SmaMzAPYINi2_8P2evaKAnMQLTgUVkctaEamO_6HJ5f5sWheV7trLekU35xPVkPwShDelefnhyJcO5yICXqXzuewBEni9LrxAEJYN2rYfiFQWJy-pDe5DPUBs-IFTpctQ","e":"AQAB","x5c":["MIIC6DCCAdCgAwIBAgIQPszqLhbrpZlE+jEJTyJg7jANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVBREZTIFNpZ25pbmcgLSBkc3RjaW1hYWQxcC5kc3QtaXRzLmRlMB4XDTE3MTIwNDE0MDEwOFoXDTE4MTIwNDE0MDEwOFowMDEuMCwGA1UEAxMlQURGUyBTaWduaW5nIC0gZHN0Y2ltYWFkMXAuZHN0LWl0cy5kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALsFVa7OTiUSni1JNB+Tf2+CW733968ZYN1iHjjvs0kj0wuaBTnOf7XtKy4FaDSSQeKzTOAOwRFyPPOcS643y95dyx6miXrb0dLXSwJ5L21ClIhsZ7hp5jrIIePdT\/eaRzyAJ4stqeuBUKAW0ydGunBeS0bjzxylV4I1408nb5iq7fyFWllA4CAkbMOQW79vdEmoZsXHTt2pEoH7GbUZEfEpmjMwD2CDYtv\/D9nr2igJzEC04FFZHLWhGpjv+hyeX+bFoXle7ay3pFN+cT1ZD8EoQ3pXn54ciXDuciAl6l87nsARJ4vS68QBCWDdq2H4hUFicvqQ3uQz1AbPiBU6XLUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAPHCisZyPf\/fuuQEW5LyzZSYMwBRYVR6kk\/M2ZNx6TrUEwmOb10RQ3G97bLAshN44g5lWdPYz4EOt6d2o71etIjf79f+IR0MAjEgBB2HThaHcMU9KG229Ftcauie9XeurngMawTRu60YqH7+go8EMf6a1Kdnx37DMy\/1LRlsYJVfEoOCab3GgcIdXrRSYWqsY4SVJZiTPYdqz9vmNPSXXiDSOTl6qXHV\/f53WTS2V5aIQbuJJziXlceusuVNny0o5h+j6ovZ1HhEGAu3lpD+8kY8KUqA4kXMH3VNZqzHBYazJx\/QBB3bG45cZSOvV3gUOnGBgiv9NBWjhvmY0fC3J6Q=="]}]}`, - Iat: int64(1513165889) + 3601, - Err: errors.New("Token is expired"), - }, - { - Name: "Valid Token with RS256 signature verified against empty JWKS document", - TokenString: "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSIsImtpZCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSJ9.eyJhdWQiOiJjaHJvbm9ncmFmIiwiaXNzIjoiaHR0cHM6Ly9kc3RjaW1hYWQxcC5kc3QtaXRzLmRlL2FkZnMiLCJpYXQiOjE1MTMxNjU4ODksImV4cCI6MTUxMzE2OTQ4OSwiYXV0aF90aW1lIjoxNTEzMTY1ODg4LCJzdWIiOiJlWVYzamRsZE55RlkxcUZGSDRvQWRCdkRGZmJWZm51RzI5SGlIa1N1andrPSIsInVwbiI6ImJzY0Bkc3QtaXRzLmRlIiwidW5pcXVlX25hbWUiOiJEU1RcXGJzYyIsInNpZCI6IlMtMS01LTIxLTI1MDUxNTEzOTgtMjY2MTAyODEwOS0zNzU0MjY1ODIwLTExMDQifQ.nK51Ui4XN45SVul9igNaKFQd-F63BNstBzW-T5LBVm_ANHCEHyP3_88C3ffkkQIi3PxYacRJGtfswP35ws7YJUcNp-GoGZARqz62NpMtbQyhos6mCaVXwPoxPbrZx4AkMQgxkZwJcOzceX7mpjcT3kCth30chN3lkhzSjGrXe4ZDOAV25liS-dsdBiqDiaTB91sS534GM76qJQxFUs51oSbYTRdCN1VJ0XopMcasfVDzFrtSbyvEIVXlpKK2HplnhheqF4QHrM_3cjV_NGRr3tYLe-AGTdDXKWlJD1GDz1ECXeMGQHPoz3U8cqNsFLYBstIlCgfnBWgWsPZSvJPJUg", - JwksDocument: "", - Iat: int64(1513165889), - Err: errors.New("unexpected end of JSON input"), - }, - { - Name: "Invalid Token", - Err: errors.New("token contains an invalid number of segments"), - }, - } - - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - // mock JWKS server - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - io.WriteString(w, tt.JwksDocument) - })) - defer ts.Close() - - j := oauth2.JWT{ - Jwksurl: ts.URL, - Now: func() time.Time { - return time.Unix(tt.Iat, 0) - }, - } - _, err := j.GetClaims(tt.TokenString) - if tt.Err != nil { - if err != nil { - if tt.Err.Error() != err.Error() { - t.Errorf("Error in test %s expected error: %v actual: %v", tt.Name, tt.Err, err) - } // else: that's what we expect - } else { - t.Errorf("Error in test %s expected error: %v actual: none", tt.Name, tt.Err) - } - } else { - if err != nil { - t.Errorf("Error in tt %s: %v", tt.Name, err) - } // else: that's what we expect - } - }) - } -} - -func TestJWT_ExtendedPrincipal(t *testing.T) { - history := time.Unix(-446774400, 0) - type fields struct { - Now func() time.Time - } - type args struct { - ctx context.Context - principal oauth2.Principal - extension time.Duration - } - tests := []struct { - name string - fields fields - args args - want oauth2.Principal - wantErr bool - }{ - { - name: "Extend principal by one hour", - fields: fields{ - Now: func() time.Time { - return history - }, - }, - args: args{ - ctx: context.Background(), - principal: oauth2.Principal{ - ExpiresAt: history, - }, - extension: time.Hour, - }, - want: oauth2.Principal{ - ExpiresAt: history.Add(time.Hour), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - j := &oauth2.JWT{ - Now: tt.fields.Now, - } - got, err := j.ExtendedPrincipal(tt.args.ctx, tt.args.principal, tt.args.extension) - if (err != nil) != tt.wantErr { - t.Errorf("JWT.ExtendedPrincipal() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("JWT.ExtendedPrincipal() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/chronograf/oauth2/mux.go b/chronograf/oauth2/mux.go deleted file mode 100644 index bc0c4132f40..00000000000 --- a/chronograf/oauth2/mux.go +++ /dev/null @@ -1,201 +0,0 @@ -package oauth2 - -import ( - "net/http" - "path" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "golang.org/x/oauth2" -) - -// Check to ensure AuthMux is an oauth2.Mux -var _ Mux = &AuthMux{} - -// TenMinutes is the default length of time to get a response back from the OAuth provider -const TenMinutes = 10 * time.Minute - -// NewAuthMux constructs a Mux handler that checks a cookie against the authenticator -func NewAuthMux(p Provider, a Authenticator, t Tokenizer, basepath string, l chronograf.Logger, UseIDToken bool) *AuthMux { - return &AuthMux{ - Provider: p, - Auth: a, - Tokens: t, - SuccessURL: path.Join(basepath, "/"), - FailureURL: path.Join(basepath, "/login"), - Now: DefaultNowTime, - Logger: l, - UseIDToken: UseIDToken, - } -} - -// AuthMux services an Oauth2 interaction with a provider and browser and -// stores the resultant token in the user's browser as a cookie. The benefit of -// this is that the cookie's authenticity can be verified independently by any -// Chronograf instance as long as the Authenticator has no external -// dependencies (e.g. on a Database). -type AuthMux struct { - Provider Provider // Provider is the OAuth2 service - Auth Authenticator // Auth is used to Authorize after successful OAuth2 callback and Expire on Logout - Tokens Tokenizer // Tokens is used to create and validate OAuth2 "state" - Logger chronograf.Logger // Logger is used to give some more information about the OAuth2 process - SuccessURL string // SuccessURL is redirect location after successful authorization - FailureURL string // FailureURL is redirect location after authorization failure - Now func() time.Time // Now returns the current time (for testing) - UseIDToken bool // UseIDToken enables OpenID id_token support -} - -// Login uses a Cookie with a random string as the state validation method. JWTs are -// a good choice here for encoding because they can be validated without -// storing state. Login returns a handler that redirects to the providers OAuth login. -func (j *AuthMux) Login() http.Handler { - conf := j.Provider.Config() - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // We are creating a token with an encoded random string to prevent CSRF attacks - // This token will be validated during the OAuth callback. - // We'll give our users 10 minutes from this point to type in their - // oauth2 provider's password. - // If the callback is not received within 10 minutes, then authorization will fail. - csrf := randomString(32) // 32 is not important... just long - now := j.Now() - - // This token will be valid for 10 minutes. Any chronograf server will - // be able to validate this token. - p := Principal{ - Subject: csrf, - IssuedAt: now, - ExpiresAt: now.Add(TenMinutes), - } - token, err := j.Tokens.Create(r.Context(), p) - - // This is likely an internal server error - if err != nil { - j.Logger. - WithField("component", "auth"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("url", r.URL). - Error("Internal authentication error: ", err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - url := conf.AuthCodeURL(string(token), oauth2.AccessTypeOnline) - http.Redirect(w, r, url, http.StatusTemporaryRedirect) - }) -} - -// Callback is used by OAuth2 provider after authorization is granted. If -// granted, Callback will set a cookie with a month-long expiration. It is -// recommended that the value of the cookie be encoded as a JWT because the JWT -// can be validated without the need for saving state. The JWT contains the -// principal's identifier (e.g. email address). -func (j *AuthMux) Callback() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log := j.Logger. - WithField("component", "auth"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("url", r.URL) - - state := r.FormValue("state") - // Check if the OAuth state token is valid to prevent CSRF - // The state variable we set is actually a token. We'll check - // if the token is valid. We don't need to know anything - // about the contents of the principal only that it hasn't expired. - if _, err := j.Tokens.ValidPrincipal(r.Context(), Token(state), TenMinutes); err != nil { - log.Error("Invalid OAuth state received: ", err.Error()) - http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) - return - } - - // Exchange the code back with the provider to the the token - conf := j.Provider.Config() - code := r.FormValue("code") - token, err := conf.Exchange(r.Context(), code) - if err != nil { - log.Error("Unable to exchange code for token ", err.Error()) - http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) - return - } - - if token.Extra("id_token") != nil && !j.UseIDToken { - log.Info("Found an extra id_token, but option --useidtoken is not set") - } - - // if we received an extra id_token, inspect it - var id string - var group string - if j.UseIDToken && token.Extra("id_token") != nil && token.Extra("id_token") != "" { - log.Debug("Found an extra id_token") - if provider, ok := j.Provider.(ExtendedProvider); ok { - log.Debug("Provider implements PrincipalIDFromClaims()") - tokenString, ok := token.Extra("id_token").(string) - if !ok { - log.Error("Cannot cast id_token as string") - http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) - return - } - claims, err := j.Tokens.GetClaims(tokenString) - if err != nil { - log.Error("Parsing extra id_token failed:", err) - http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) - return - } - log.Debug("Found claims: ", claims) - id, err = provider.PrincipalIDFromClaims(claims) - if err != nil { - log.Error("Requested claim not found in id_token:", err) - http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) - return - } - group, err = provider.GroupFromClaims(claims) - if err != nil { - log.Error("Requested claim not found in id_token:", err) - http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) - return - } - } else { - log.Debug("Provider does not implement PrincipalIDFromClaims()") - } - } else { - // otherwise perform an additional lookup - oauthClient := conf.Client(r.Context(), token) - // Using the token get the principal identifier from the provider - id, err = j.Provider.PrincipalID(oauthClient) - if err != nil { - log.Error("Unable to get principal identifier ", err.Error()) - http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) - return - } - group, err = j.Provider.Group(oauthClient) - if err != nil { - log.Error("Unable to get OAuth Group", err.Error()) - http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) - return - } - } - - p := Principal{ - Subject: id, - Issuer: j.Provider.Name(), - Group: group, - } - ctx := r.Context() - err = j.Auth.Authorize(ctx, w, p) - if err != nil { - log.Error("Unable to get add session to response ", err.Error()) - http.Redirect(w, r, j.FailureURL, http.StatusTemporaryRedirect) - return - } - log.Info("User ", id, " is authenticated") - http.Redirect(w, r, j.SuccessURL, http.StatusTemporaryRedirect) - }) -} - -// Logout handler will expire our authentication cookie and redirect to the successURL -func (j *AuthMux) Logout() http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - j.Auth.Expire(w) - http.Redirect(w, r, j.SuccessURL, http.StatusTemporaryRedirect) - }) -} diff --git a/chronograf/oauth2/mux_test.go b/chronograf/oauth2/mux_test.go deleted file mode 100644 index 964cb08afdc..00000000000 --- a/chronograf/oauth2/mux_test.go +++ /dev/null @@ -1,221 +0,0 @@ -package oauth2 - -import ( - "encoding/json" - "net/http" - "net/http/cookiejar" - "net/http/httptest" - "net/url" - "testing" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -var testTime = time.Date(1985, time.October, 25, 18, 0, 0, 0, time.UTC) - -type mockCallbackResponse struct { - AccessToken string `json:"access_token"` -} - -// setupMuxTest produces an http.Client and an httptest.Server configured to -// use a particular http.Handler selected from a AuthMux. As this selection is -// done during the setup process, this configuration is performed by providing -// a function, and returning the desired handler. Cleanup is still the -// responsibility of the test writer, so the httptest.Server's Close() method -// should be deferred. -func setupMuxTest(response interface{}, selector func(*AuthMux) http.Handler) (*http.Client, *httptest.Server, *httptest.Server) { - provider := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Header().Set("content-type", "application/json") - rw.WriteHeader(http.StatusOK) - - body, _ := json.Marshal(response) - - rw.Write(body) - })) - - now := func() time.Time { - return testTime - } - mp := &MockProvider{ - Email: "biff@example.com", - ProviderURL: provider.URL, - Orgs: "", - } - mt := &YesManTokenizer{} - auth := &cookie{ - Name: DefaultCookieName, - Lifespan: 1 * time.Hour, - Inactivity: DefaultInactivityDuration, - Now: now, - Tokens: mt, - } - - useidtoken := false - - jm := NewAuthMux(mp, auth, mt, "", &chronograf.NoopLogger{}, useidtoken) - ts := httptest.NewServer(selector(jm)) - jar, _ := cookiejar.New(nil) - hc := http.Client{ - Jar: jar, - CheckRedirect: func(r *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - } - return &hc, ts, provider -} - -// teardownMuxTest cleans up any resources created by setupMuxTest. This should -// be deferred in your test after setupMuxTest is called -func teardownMuxTest(hc *http.Client, backend *httptest.Server, provider *httptest.Server) { - provider.Close() - backend.Close() -} - -func Test_AuthMux_Logout_DeletesSessionCookie(t *testing.T) { - t.Parallel() - - var response interface{} - - hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler { - return j.Logout() - }) - defer teardownMuxTest(hc, ts, prov) - - tsURL, _ := url.Parse(ts.URL) - - hc.Jar.SetCookies(tsURL, []*http.Cookie{ - &http.Cookie{ - Name: DefaultCookieName, - Value: "", - }, - }) - - resp, err := hc.Get(ts.URL) - if err != nil { - t.Fatal("Error communicating with Logout() handler: err:", err) - } - - if resp.StatusCode < 300 || resp.StatusCode >= 400 { - t.Fatal("Expected to be redirected, but received status code", resp.StatusCode) - } - - cookies := resp.Cookies() - if len(cookies) != 1 { - t.Fatal("Expected that cookie would be present but wasn't") - } - - c := cookies[0] - if c.Name != DefaultCookieName || c.Expires != testTime.Add(-1*time.Hour) { - t.Fatal("Expected cookie to be expired but wasn't") - } -} - -func Test_AuthMux_Login_RedirectsToCorrectURL(t *testing.T) { - t.Parallel() - - var response interface{} - - hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler { - return j.Login() // Use Login handler for httptest server. - }) - defer teardownMuxTest(hc, ts, prov) - - resp, err := hc.Get(ts.URL) - if err != nil { - t.Fatal("Error communicating with Login() handler: err:", err) - } - - // Ensure we were redirected - if resp.StatusCode < 300 || resp.StatusCode >= 400 { - t.Fatal("Expected to be redirected, but received status code", resp.StatusCode) - } - - loc, err := resp.Location() - if err != nil { - t.Fatal("Expected a location to be redirected to, but wasn't present") - } - - if state := loc.Query().Get("state"); state != "HELLO?!MCFLY?!ANYONEINTHERE?!" { - t.Fatalf("Expected state to be %s set but was %s", "HELLO?!MCFLY?!ANYONEINTHERE?!", state) - } -} - -func Test_AuthMux_Callback_SetsCookie(t *testing.T) { - response := mockCallbackResponse{AccessToken: "123"} - hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler { - return j.Callback() - }) - defer teardownMuxTest(hc, ts, prov) - - tsURL, _ := url.Parse(ts.URL) - - v := url.Values{ - "code": {"4815162342"}, - "state": {"foobar"}, - } - - tsURL.RawQuery = v.Encode() - - resp, err := hc.Get(tsURL.String()) - if err != nil { - t.Fatal("Error communicating with Callback() handler: err", err) - } - - // Ensure we were redirected - if resp.StatusCode < 300 || resp.StatusCode >= 400 { - t.Fatal("Expected to be redirected, but received status code", resp.StatusCode) - } - - // Check that cookie was set - cookies := resp.Cookies() - if count := len(cookies); count != 1 { - t.Fatal("Expected exactly one cookie to be set but found", count) - } - - c := cookies[0] - - if c.Name != DefaultCookieName { - t.Fatal("Expected cookie to be named", DefaultCookieName, "but was", c.Name) - } -} - -func Test_AuthMux_Callback_HandlesIdToken(t *testing.T) { - // body taken from ADFS4 - response := mockCallbackResponse{AccessToken: `eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSJ9.eyJhdWQiOiJ1cm46bWljcm9zb2Z0OnVzZXJpbmZvIiwiaXNzIjoiaHR0cDovL2RzdGNpbWFhZDFwLmRzdC1pdHMuZGUvYWRmcy9zZXJ2aWNlcy90cnVzdCIsImlhdCI6MTUxNTcwMDU2NSwiZXhwIjoxNTE1NzA0MTY1LCJhcHB0eXBlIjoiQ29uZmlkZW50aWFsIiwiYXBwaWQiOiJjaHJvbm9ncmFmIiwiYXV0aG1ldGhvZCI6InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlBhc3N3b3JkUHJvdGVjdGVkVHJhbnNwb3J0IiwiYXV0aF90aW1lIjoiMjAxOC0wMS0xMVQxOTo1MToyNS44MDZaIiwidmVyIjoiMS4wIiwic2NwIjoib3BlbmlkIiwic3ViIjoiZVlWM2pkbGROeUZZMXFGRkg0b0FkQnZERmZiVmZudUcyOUhpSGtTdWp3az0ifQ.sf1qJys9LMUp2S232IRK2aTXiPCE93O-cUdYQQz7kg2woyD46KLwwKIYJVqMaqLspTn3OmaIhKtgx5ZXyAEtihODB1GOBK7DBNRBYCS1iqY_v2-Qwjf7hgaNaCqBjs0DZJspfp5G9MTykvD1FOtQNjPOcBW-i2bblG9L9jlmMbOZ3F7wrZMrroTSkiSn_gRiw2SnN8K7w8WrMEXNK2_jg9ZJ7aSHeUSBwkRNFRds2QNho3HWHg-zcsZFdZ4UGSt-6Az_0LY3yENMLj5us5Rl6Qzk_Re2dhFrlnlXlY1v1DEp3icCvvjkv6AeZWjTfW4qETZaCXUKtSyZ7d5_V1CRDQ", "token_type": "bearer", "expires_in": 3600, "resource": "urn:microsoft:userinfo", "refresh_token": "X9ZGO4H1bMk2bFeOfpv18BzAuFBzUPKQNfOEfdp60FkAAQAALPEBfj23FPEzajle-hm4DrDXp8-Kj53OqoVGalyZeuR-lfJzxpQXQhRAXZOUTuuQ8AQycByh9AylQYDA0jdMFW4FL4WL_6JhNh2JrtXCv2HQ9ozbUq9F7u_O0cY7u0P2pfNujQfk3ckYn-CMVjXbuwJTve6bXUR0JDp5c195bAVA5eFWyI-2uh432t7viyaIjAVbWxQF4fvimcpF1Et9cGodZHVsrZzGxKRnzwjYkWHsqm9go4KOeSKN6MlcWbjvS1UdMjQXSvoqSI00JnSMC3hxJZFn5JcmAPB1AMnJf4VvXZ5b-aOnwdX09YT8KayWkWekAsuZqTAsFwhZPVCRGWAFAADy0e2fTe6l-U6Cj_2bWsq6Snm1QEpWHXuwOJKWZJH-9yQn8KK3KzRowSzRuACzEIpZS5skrqXs_-2aOaZibNpjCEVyw8fF8GTw3VRLufsSrMQ5pD0KL7TppTGFpaqgwIH1yq6T8aRY4DeyoJkNpnO9cw1wuqnY7oGF-J25sfZ4XNWhk6o5e9A45PXhTilClyDKDLqTfdoIsG1Koc2ywqTIb-XI_EbWR3e4ijy8Kmlehw1kU9_xAG0MmmD2HTyGHZCBRgrskYCcHd-UNgCMrNAb5dZQ8NwpKtEL46qIq4R0lheTRRK8sOWzzuJXmvDEoJiIxqSR3Ma4MOISi-vsIsAuiEL9G1aMOkDRj-kDVmqrdKRAwYnN78AWY5EFfkQJyVBbiG882wBh9S0q3HUUCxzFerOvl4eDlVn6m18rRMz7CVZYBBltGtHRhEOQ4gumICR5JRrXAC50aBmUlhDiiMdbEIwJrvWrkhKE0oAJznqC7gleP0E4EOEh9r6CEGZ7Oj8X9Cdzjbuq2G1JGBm_yUvkhAcV61DjOiIQl35BpOfshveNZf_caUtNMa2i07BBmezve17-2kWGzRunr1BD1vMTz41z-H62fy4McR47WJjdDJnuy4DH5AZYQ6ooVxWCtEqeqRPYpzO0XdOdJGXFqXs9JzDKVXTgnHU443hZBC5H-BJkZDuuJ_ZWNKXf03JhouWkxXcdaMbuaQYOZJsUySVyJ5X4usrBFjW4udZAzy7mua-nJncbvcwoyVXiFlRfZiySXolQ9865N7XUnEk_2PijMLoVDATDbA09XuRySvngNsdsQ27p21dPxChXdtpD5ofNqKJ2FBzFKmxCkuX7L01N1nDpWQTuxhHF0JfxSKG5m3jcTx8Bd7Un94mTuAB7RuglDqkdQB9o4X9NHNGSdqGQaK-xeKoNCFWevk3VZoDoY9w2NqSNV2VIuqhy7SxtDSMjZKC5kiQi5EfGeTYZAvTwMYwaXb7K4WWtscy_ZE15EOCVeYi0hM1Ma8iFFTANkSRyX83Ju4SRphxRKnpKcJ2pPYH784I5HOm5sclhUL3aLeAA161QgxRBSa9YVIZfyXHyWQTcbNucNdhmdUZnKfRv1xtXcS9VAx2yAkoKFehZivEINX0Y500-WZ1eT_RXp0BfCKmJQ8Fu50oTaI-c5h2Q3Gp_LTSODNnMrjJiJxCLD_LD1fd1e8jTYDV3NroGlpWTuTdjMUm-Z1SMXaaJzQGEnNT6F8b6un9228L6YrDC_3MJ5J80VAHL5EO1GesdEWblugCL7AQDtFjNXq0lK8Aoo8X9_hlvDwgfdR16l8QALPT1HJVzlHPG8G3dRe50TKZnl3obU0WXN1KYG1EC4Qa3LyaVCIuGJYOeFqjMINrf7PoM368nS9yhrY08nnoHZbQ7IeA1KsNq2kANeH1doCNfWrXDwn8KxjYxZPEnzvlQ5M1RIzArOqzWL8NbftW1q2yCZZ4RVg0vOTVXsqWFnQIvWK-mkELa7bvByFzbtVHOJpc_2EKBKBNv6IYUENRCu2TOf6w7u42yvng7ccoXRTiUFUlKgVmswf9FzISxFd-YKgrzp3bMhC3gReGqcJuqEwnXPvOAY_BAkVMSd_ZaCFuyclRjFvUxrAg1T_cqOvRIlJ2Qq7z4u7W3BAo9BtFdj8QNLKJXtvvzXTprglRPDNP_QEPAkwZ_Uxa13vdYFcG18WCx4GbWQXchl5B7DnISobcdCH34M-I0xDZN98VWQVmLAfPniDUD30C8pfiYF7tW_EVy958Eg_JWVy0SstYEhV-y-adrJ1Oimjv0ptsWv-yErKBUD14aex9A_QqdnTXZUg.tqMb72eWAkAIvInuLp57NDyGxfYvms3NnhN-mllkYb7Xpd8gVbQFc2mYdzOOhtnfGuakyXYF4rZdJonQwzBO6C9KYuARciUU1Ms4bWPC-aeNO5t-aO_bDZbwC9qMPmq5ZuxG633BARGaw26fr0Z7qhcJMiou_EuaIehYTKkPB-mxtRAhxxyX91qqe0-PJnCHWoxizC4hDCUwp9Jb54tNf34BG3vtkXFX-kUARNfGucgKUkh6RYkhWiMBsMVoyWmkFXB5fYxmCAH5c5wDW6srKdyIDEWZInliuKbYR0p66vg1FfoSi4bBfrsm5NtCtLKG9V6Q0FEIA6tRRgHmKUGpkw", "refresh_token_expires_in": 28519, "scope": "openid", "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSIsImtpZCI6IllEQlVocWRXa3NLWGRHdVgwc3l0amFVdXhoQSJ9.eyJhdWQiOiJjaHJvbm9ncmFmIiwiaXNzIjoiaHR0cHM6Ly9kc3RjaW1hYWQxcC5kc3QtaXRzLmRlL2FkZnMiLCJpYXQiOjE1MTU3MDA1NjUsImV4cCI6MTUxNTcwNDE2NSwiYXV0aF90aW1lIjoxNTE1NzAwMjg1LCJzdWIiOiJlWVYzamRsZE55RlkxcUZGSDRvQWRCdkRGZmJWZm51RzI5SGlIa1N1andrPSIsInVwbiI6ImJzY0Bkc3QtaXRzLmRlIiwidW5pcXVlX25hbWUiOiJEU1RcXGJzYyIsInNpZCI6IlMtMS01LTIxLTI1MDUxNTEzOTgtMjY2MTAyODEwOS0zNzU0MjY1ODIwLTExMDQifQ.XD873K6NVRTJY1700NsflLJGZKFHJfNBjB81SlADVdAHbhnq7wkAZbGEEm8wFqvTKKysUl9EALzmDa2tR9nzohVvmHftIYBO0E-wPBzdzWWX0coEgpVAc-SysP-eIQWLsj8EaodaMkCgKO0FbTWOf4GaGIBZGklrr9EEk8VRSdbXbm6Sv9WVphezEzxq6JJBRBlCVibCnZjR5OYh1Vw_7E7P38ESPbpLY3hYYl2hz4y6dQJqCwGr7YP8KrDlYtbosZYgT7ayxokEJI1udEbX5PbAq5G6mj5rLfSOl85rMg-psZiivoM8dn9lEl2P7oT8rAvMWvQp-FIRQQHwqf9cxw`} - hc, ts, prov := setupMuxTest(response, func(j *AuthMux) http.Handler { - return j.Callback() - }) - defer teardownMuxTest(hc, ts, prov) - - tsURL, _ := url.Parse(ts.URL) - - v := url.Values{ - "code": {"4815162342"}, - "state": {"foobar"}, - } - - tsURL.RawQuery = v.Encode() - - resp, err := hc.Get(tsURL.String()) - if err != nil { - t.Fatal("Error communicating with Callback() handler: err", err) - } - - // Ensure we were redirected - if resp.StatusCode < 300 || resp.StatusCode >= 400 { - t.Fatal("Expected to be redirected, but received status code", resp.StatusCode) - } - - // Check that cookie was set - cookies := resp.Cookies() - if count := len(cookies); count != 1 { - t.Fatal("Expected exactly one cookie to be set but found", count) - } - - c := cookies[0] - - if c.Name != DefaultCookieName { - t.Fatal("Expected cookie to be named", DefaultCookieName, "but was", c.Name) - } -} diff --git a/chronograf/oauth2/oauth2.go b/chronograf/oauth2/oauth2.go deleted file mode 100644 index 5788a882fd9..00000000000 --- a/chronograf/oauth2/oauth2.go +++ /dev/null @@ -1,101 +0,0 @@ -package oauth2 - -import ( - "context" - "errors" - "net/http" - "time" - - gojwt "github.com/dgrijalva/jwt-go" - "golang.org/x/oauth2" -) - -type principalKey string - -func (p principalKey) String() string { - return string(p) -} - -var ( - // PrincipalKey is used to pass principal - // via context.Context to request-scoped - // functions. - PrincipalKey = principalKey("principal") - // ErrAuthentication means that oauth2 exchange failed - ErrAuthentication = errors.New("user not authenticated") - // ErrOrgMembership means that the user is not in the OAuth2 filtered group - ErrOrgMembership = errors.New("not a member of the required organization") -) - -/* Types */ - -// Principal is any entity that can be authenticated -type Principal struct { - Subject string - Issuer string - Organization string - Group string - ExpiresAt time.Time - IssuedAt time.Time -} - -/* Interfaces */ - -// Provider are the common parameters for all providers (RFC 6749) -type Provider interface { - // ID is issued to the registered client by the authorization (RFC 6749 Section 2.2) - ID() string - // Secret associated is with the ID (Section 2.2) - Secret() string - // Scopes is used by the authorization server to "scope" responses (Section 3.3) - Scopes() []string - // Config is the OAuth2 configuration settings for this provider - Config() *oauth2.Config - // PrincipalID with fetch the identifier to be associated with the principal. - PrincipalID(provider *http.Client) (string, error) - // Name is the name of the Provider - Name() string - // Group is a comma delimited list of groups and organizations for a provider - // TODO: This will break if there are any group names that contain commas. - // I think this is okay, but I'm not 100% certain. - Group(provider *http.Client) (string, error) -} - -// Mux is a collection of handlers responsible for servicing an Oauth2 interaction between a browser and a provider -type Mux interface { - Login() http.Handler - Logout() http.Handler - Callback() http.Handler -} - -// Authenticator represents a service for authenticating users. -type Authenticator interface { - // Validate returns Principal associated with authenticated and authorized - // entity if successful. - Validate(context.Context, *http.Request) (Principal, error) - // Authorize will grant privileges to a Principal - Authorize(context.Context, http.ResponseWriter, Principal) error - // Extend will extend the lifetime of a already validated Principal - Extend(context.Context, http.ResponseWriter, Principal) (Principal, error) - // Expire revokes privileges from a Principal - Expire(http.ResponseWriter) -} - -// Token represents a time-dependent reference (i.e. identifier) that maps back -// to the sensitive data through a tokenization system -type Token string - -// Tokenizer substitutes a sensitive data element (Principal) with a -// non-sensitive equivalent, referred to as a token, that has no extrinsic -// or exploitable meaning or value. -type Tokenizer interface { - // Create issues a token at Principal's IssuedAt that lasts until Principal's ExpireAt - Create(context.Context, Principal) (Token, error) - // ValidPrincipal checks if the token has a valid Principal and requires - // a lifespan duration to ensure it complies with possible server runtime arguments. - ValidPrincipal(ctx context.Context, token Token, lifespan time.Duration) (Principal, error) - // ExtendedPrincipal adds the extention to the principal's lifespan. - ExtendedPrincipal(ctx context.Context, principal Principal, extension time.Duration) (Principal, error) - // GetClaims returns a map with verified claims - GetClaims(tokenString string) (gojwt.MapClaims, error) -} diff --git a/chronograf/oauth2/oauth2_test.go b/chronograf/oauth2/oauth2_test.go deleted file mode 100644 index 13034892980..00000000000 --- a/chronograf/oauth2/oauth2_test.go +++ /dev/null @@ -1,128 +0,0 @@ -package oauth2 - -import ( - "context" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "time" - - goauth "golang.org/x/oauth2" - - gojwt "github.com/dgrijalva/jwt-go" - "github.com/influxdata/influxdb/v2/chronograf" -) - -var _ Provider = &MockProvider{} - -type MockProvider struct { - Email string - Orgs string - - ProviderURL string -} - -func (mp *MockProvider) Config() *goauth.Config { - return &goauth.Config{ - RedirectURL: "http://www.example.com", - ClientID: "4815162342", - ClientSecret: "8675309", - Endpoint: goauth.Endpoint{ - AuthURL: mp.ProviderURL + "/oauth/auth", - TokenURL: mp.ProviderURL + "/oauth/token", - }, - } -} - -func (mp *MockProvider) ID() string { - return "8675309" -} - -func (mp *MockProvider) Name() string { - return "mockly" -} - -func (mp *MockProvider) PrincipalID(provider *http.Client) (string, error) { - return mp.Email, nil -} - -func (mp *MockProvider) PrincipalIDFromClaims(claims gojwt.MapClaims) (string, error) { - return mp.Email, nil -} - -func (mp *MockProvider) GroupFromClaims(claims gojwt.MapClaims) (string, error) { - email := strings.Split(mp.Email, "@") - if len(email) != 2 { - //g.Logger.Error("malformed email address, expected %q to contain @ symbol", id) - return "DEFAULT", nil - } - - return email[1], nil -} - -func (mp *MockProvider) Group(provider *http.Client) (string, error) { - return mp.Orgs, nil -} - -func (mp *MockProvider) Scopes() []string { - return []string{} -} - -func (mp *MockProvider) Secret() string { - return "4815162342" -} - -var _ Tokenizer = &YesManTokenizer{} - -type YesManTokenizer struct{} - -func (y *YesManTokenizer) ValidPrincipal(ctx context.Context, token Token, duration time.Duration) (Principal, error) { - return Principal{ - Subject: "biff@example.com", - Issuer: "Biff Tannen's Pleasure Paradise", - }, nil -} - -func (y *YesManTokenizer) Create(ctx context.Context, p Principal) (Token, error) { - return Token("HELLO?!MCFLY?!ANYONEINTHERE?!"), nil -} - -func (y *YesManTokenizer) ExtendedPrincipal(ctx context.Context, p Principal, ext time.Duration) (Principal, error) { - return p, nil -} - -func (y *YesManTokenizer) GetClaims(tokenString string) (gojwt.MapClaims, error) { - return gojwt.MapClaims{}, nil -} - -func NewTestTripper(log chronograf.Logger, ts *httptest.Server, rt http.RoundTripper) (*TestTripper, error) { - url, err := url.Parse(ts.URL) - if err != nil { - return nil, err - } - return &TestTripper{log, rt, url}, nil -} - -type TestTripper struct { - Log chronograf.Logger - - rt http.RoundTripper - tsURL *url.URL -} - -// RoundTrip modifies the Hostname of the incoming request to be directed to the -// test server. -func (tt *TestTripper) RoundTrip(r *http.Request) (*http.Response, error) { - tt.Log. - WithField("component", "test"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("url", r.URL). - Info("Request") - - r.URL.Host = tt.tsURL.Host - r.URL.Scheme = tt.tsURL.Scheme - - return tt.rt.RoundTrip(r) -} diff --git a/chronograf/oauth2/time.go b/chronograf/oauth2/time.go deleted file mode 100644 index 529e1c4b70d..00000000000 --- a/chronograf/oauth2/time.go +++ /dev/null @@ -1,6 +0,0 @@ -package oauth2 - -import "time" - -// DefaultNowTime returns UTC time at the present moment -var DefaultNowTime = func() time.Time { return time.Now().UTC() } diff --git a/chronograf/organizations/dashboards.go b/chronograf/organizations/dashboards.go deleted file mode 100644 index 1b79b30a119..00000000000 --- a/chronograf/organizations/dashboards.go +++ /dev/null @@ -1,112 +0,0 @@ -package organizations - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure that DashboardsStore implements chronograf.DashboardStore -var _ chronograf.DashboardsStore = &DashboardsStore{} - -// DashboardsStore facade on a DashboardStore that filters dashboards -// by organization. -type DashboardsStore struct { - store chronograf.DashboardsStore - organization string -} - -// NewDashboardsStore creates a new DashboardsStore from an existing -// chronograf.DashboardStore and an organization string -func NewDashboardsStore(s chronograf.DashboardsStore, org string) *DashboardsStore { - return &DashboardsStore{ - store: s, - organization: org, - } -} - -// All retrieves all dashboards from the underlying DashboardStore and filters them -// by organization. -func (s *DashboardsStore) All(ctx context.Context) ([]chronograf.Dashboard, error) { - err := validOrganization(ctx) - if err != nil { - return nil, err - } - - ds, err := s.store.All(ctx) - if err != nil { - return nil, err - } - - // This filters dashboards without allocating - // https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating - dashboards := ds[:0] - for _, d := range ds { - if d.Organization == s.organization { - dashboards = append(dashboards, d) - } - } - - return dashboards, nil -} - -// Add creates a new Dashboard in the DashboardsStore with dashboard.Organization set to be the -// organization from the dashboard store. -func (s *DashboardsStore) Add(ctx context.Context, d chronograf.Dashboard) (chronograf.Dashboard, error) { - err := validOrganization(ctx) - if err != nil { - return chronograf.Dashboard{}, err - } - - d.Organization = s.organization - return s.store.Add(ctx, d) -} - -// Delete the dashboard from DashboardsStore -func (s *DashboardsStore) Delete(ctx context.Context, d chronograf.Dashboard) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - d, err = s.store.Get(ctx, d.ID) - if err != nil { - return err - } - - return s.store.Delete(ctx, d) -} - -// Get returns a Dashboard if the id exists and belongs to the organization that is set. -func (s *DashboardsStore) Get(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - err := validOrganization(ctx) - if err != nil { - return chronograf.Dashboard{}, err - } - - d, err := s.store.Get(ctx, id) - if err != nil { - return chronograf.Dashboard{}, err - } - - if d.Organization != s.organization { - return chronograf.Dashboard{}, chronograf.ErrDashboardNotFound - } - - return d, nil -} - -// Update the dashboard in DashboardsStore. -func (s *DashboardsStore) Update(ctx context.Context, d chronograf.Dashboard) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - _, err = s.store.Get(ctx, d.ID) - if err != nil { - return err - } - - return s.store.Update(ctx, d) -} diff --git a/chronograf/organizations/dashboards_test.go b/chronograf/organizations/dashboards_test.go deleted file mode 100644 index 8b929bd8d0a..00000000000 --- a/chronograf/organizations/dashboards_test.go +++ /dev/null @@ -1,342 +0,0 @@ -package organizations_test - -import ( - "context" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/organizations" -) - -// IgnoreFields is used because ID cannot be predicted reliably -// EquateEmpty is used because we want nil slices, arrays, and maps to be equal to the empty map -var dashboardCmpOptions = cmp.Options{ - cmpopts.EquateEmpty(), - cmpopts.IgnoreFields(chronograf.Dashboard{}, "ID"), -} - -func TestDashboards_All(t *testing.T) { - type fields struct { - DashboardsStore chronograf.DashboardsStore - } - type args struct { - organization string - } - tests := []struct { - name string - args args - fields fields - want []chronograf.Dashboard - wantErr bool - }{ - { - name: "No Dashboards", - fields: fields{ - DashboardsStore: &mocks.DashboardsStore{ - AllF: func(ctx context.Context) ([]chronograf.Dashboard, error) { - return nil, fmt.Errorf("no Dashboards") - }, - }, - }, - wantErr: true, - }, - { - name: "All Dashboards", - fields: fields{ - DashboardsStore: &mocks.DashboardsStore{ - AllF: func(ctx context.Context) ([]chronograf.Dashboard, error) { - return []chronograf.Dashboard{ - { - Name: "howdy", - Organization: "1337", - }, - { - Name: "doody", - Organization: "1338", - }, - }, nil - }, - }, - }, - args: args{ - organization: "1337", - }, - want: []chronograf.Dashboard{ - { - Name: "howdy", - Organization: "1337", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := organizations.NewDashboardsStore(tt.fields.DashboardsStore, tt.args.organization) - ctx := context.WithValue(context.Background(), organizations.ContextKey, tt.args.organization) - gots, err := s.All(ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. DashboardsStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - return - } - for i, got := range gots { - if diff := cmp.Diff(got, tt.want[i], dashboardCmpOptions...); diff != "" { - t.Errorf("%q. DashboardsStore.All():\n-got/+want\ndiff %s", tt.name, diff) - } - } - }) - } -} - -func TestDashboards_Add(t *testing.T) { - type fields struct { - DashboardsStore chronograf.DashboardsStore - } - type args struct { - organization string - ctx context.Context - dashboard chronograf.Dashboard - } - tests := []struct { - name string - args args - fields fields - want chronograf.Dashboard - wantErr bool - }{ - { - name: "Add Dashboard", - fields: fields{ - DashboardsStore: &mocks.DashboardsStore{ - AddF: func(ctx context.Context, s chronograf.Dashboard) (chronograf.Dashboard, error) { - return s, nil - }, - GetF: func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - dashboard: chronograf.Dashboard{ - ID: 1229, - Name: "howdy", - }, - }, - want: chronograf.Dashboard{ - Name: "howdy", - Organization: "1337", - }, - }, - } - for _, tt := range tests { - s := organizations.NewDashboardsStore(tt.fields.DashboardsStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - d, err := s.Add(tt.args.ctx, tt.args.dashboard) - if (err != nil) != tt.wantErr { - t.Errorf("%q. DashboardsStore.Add() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - got, err := s.Get(tt.args.ctx, d.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, tt.want, dashboardCmpOptions...); diff != "" { - t.Errorf("%q. DashboardsStore.Add():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestDashboards_Delete(t *testing.T) { - type fields struct { - DashboardsStore chronograf.DashboardsStore - } - type args struct { - organization string - ctx context.Context - dashboard chronograf.Dashboard - } - tests := []struct { - name string - fields fields - args args - addFirst bool - wantErr bool - }{ - { - name: "Delete dashboard", - fields: fields{ - DashboardsStore: &mocks.DashboardsStore{ - DeleteF: func(ctx context.Context, s chronograf.Dashboard) error { - return nil - }, - GetF: func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - dashboard: chronograf.Dashboard{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - }, - addFirst: true, - }, - } - for _, tt := range tests { - s := organizations.NewDashboardsStore(tt.fields.DashboardsStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - err := s.Delete(tt.args.ctx, tt.args.dashboard) - if (err != nil) != tt.wantErr { - t.Errorf("%q. DashboardsStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - } -} - -func TestDashboards_Get(t *testing.T) { - type fields struct { - DashboardsStore chronograf.DashboardsStore - } - type args struct { - organization string - ctx context.Context - dashboard chronograf.Dashboard - } - tests := []struct { - name string - fields fields - args args - want chronograf.Dashboard - wantErr bool - }{ - { - name: "Get Dashboard", - fields: fields{ - DashboardsStore: &mocks.DashboardsStore{ - GetF: func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - dashboard: chronograf.Dashboard{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - }, - want: chronograf.Dashboard{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - }, - } - for _, tt := range tests { - s := organizations.NewDashboardsStore(tt.fields.DashboardsStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - got, err := s.Get(tt.args.ctx, tt.args.dashboard.ID) - if (err != nil) != tt.wantErr { - t.Errorf("%q. DashboardsStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if diff := cmp.Diff(got, tt.want, dashboardCmpOptions...); diff != "" { - t.Errorf("%q. DashboardsStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestDashboards_Update(t *testing.T) { - type fields struct { - DashboardsStore chronograf.DashboardsStore - } - type args struct { - organization string - ctx context.Context - dashboard chronograf.Dashboard - name string - } - tests := []struct { - name string - fields fields - args args - want chronograf.Dashboard - addFirst bool - wantErr bool - }{ - { - name: "Update Dashboard Name", - fields: fields{ - DashboardsStore: &mocks.DashboardsStore{ - UpdateF: func(ctx context.Context, s chronograf.Dashboard) error { - return nil - }, - GetF: func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{ - ID: 1229, - Name: "doody", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - dashboard: chronograf.Dashboard{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - name: "doody", - }, - want: chronograf.Dashboard{ - Name: "doody", - Organization: "1337", - }, - addFirst: true, - }, - } - for _, tt := range tests { - if tt.args.name != "" { - tt.args.dashboard.Name = tt.args.name - } - s := organizations.NewDashboardsStore(tt.fields.DashboardsStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - err := s.Update(tt.args.ctx, tt.args.dashboard) - if (err != nil) != tt.wantErr { - t.Errorf("%q. DashboardsStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - got, err := s.Get(tt.args.ctx, tt.args.dashboard.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, tt.want, dashboardCmpOptions...); diff != "" { - t.Errorf("%q. DashboardsStore.Update():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} diff --git a/chronograf/organizations/org_config.go b/chronograf/organizations/org_config.go deleted file mode 100644 index 1378a1e6eb3..00000000000 --- a/chronograf/organizations/org_config.go +++ /dev/null @@ -1,51 +0,0 @@ -package organizations - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure that OrganizationConfig implements chronograf.OrganizationConfigStore -var _ chronograf.OrganizationConfigStore = &OrganizationConfigStore{} - -// OrganizationConfigStore facade on a OrganizationConfig that filters OrganizationConfigs by organization. -type OrganizationConfigStore struct { - store chronograf.OrganizationConfigStore - organization string -} - -// NewOrganizationConfigStore creates a new OrganizationConfigStore from an existing -// chronograf.OrganizationConfigStore and an organization string -func NewOrganizationConfigStore(s chronograf.OrganizationConfigStore, orgID string) *OrganizationConfigStore { - return &OrganizationConfigStore{ - store: s, - organization: orgID, - } -} - -// FindOrCreate gets an organization's config or creates one if none exists -func (s *OrganizationConfigStore) FindOrCreate(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - var err = validOrganization(ctx) - if err != nil { - return nil, err - } - - oc, err := s.store.FindOrCreate(ctx, orgID) - if err != nil { - return nil, err - } - - return oc, nil - -} - -// Put the OrganizationConfig in OrganizationConfigStore. -func (s *OrganizationConfigStore) Put(ctx context.Context, c *chronograf.OrganizationConfig) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - return s.store.Put(ctx, c) -} diff --git a/chronograf/organizations/organizations.go b/chronograf/organizations/organizations.go deleted file mode 100644 index 57a7ee1b7f2..00000000000 --- a/chronograf/organizations/organizations.go +++ /dev/null @@ -1,158 +0,0 @@ -package organizations - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -type contextKey string - -// ContextKey is the key used to specify the -// organization via context -const ContextKey = contextKey("organization") - -func validOrganization(ctx context.Context) error { - // prevents panic in case of nil context - if ctx == nil { - return fmt.Errorf("expect non nil context") - } - orgID, ok := ctx.Value(ContextKey).(string) - // should never happen - if !ok { - return fmt.Errorf("expected organization key to be a string") - } - if orgID == "" { - return fmt.Errorf("expected organization key to be set") - } - return nil -} - -// ensure that OrganizationsStore implements chronograf.OrganizationStore -var _ chronograf.OrganizationsStore = &OrganizationsStore{} - -// OrganizationsStore facade on a OrganizationStore that filters organizations -// by organization. -type OrganizationsStore struct { - store chronograf.OrganizationsStore - organization string -} - -// NewOrganizationsStore creates a new OrganizationsStore from an existing -// chronograf.OrganizationStore and an organization string -func NewOrganizationsStore(s chronograf.OrganizationsStore, org string) *OrganizationsStore { - return &OrganizationsStore{ - store: s, - organization: org, - } -} - -// All retrieves all organizations from the underlying OrganizationStore and filters them -// by organization. -func (s *OrganizationsStore) All(ctx context.Context) ([]chronograf.Organization, error) { - err := validOrganization(ctx) - if err != nil { - return nil, err - } - - ds, err := s.store.All(ctx) - if err != nil { - return nil, err - } - - defaultOrg, err := s.store.DefaultOrganization(ctx) - if err != nil { - return nil, err - } - - defaultOrgID := defaultOrg.ID - - // This filters organizations without allocating - // https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating - organizations := ds[:0] - for _, d := range ds { - id := d.ID - switch id { - case s.organization, defaultOrgID: - organizations = append(organizations, d) - default: - continue - } - } - - return organizations, nil -} - -// Add creates a new Organization in the OrganizationsStore with organization.Organization set to be the -// organization from the organization store. -func (s *OrganizationsStore) Add(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { - return nil, fmt.Errorf("cannot create organization") -} - -// Delete the organization from OrganizationsStore -func (s *OrganizationsStore) Delete(ctx context.Context, o *chronograf.Organization) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - o, err = s.store.Get(ctx, chronograf.OrganizationQuery{ID: &o.ID}) - if err != nil { - return err - } - - return s.store.Delete(ctx, o) -} - -// Get returns a Organization if the id exists and belongs to the organization that is set. -func (s *OrganizationsStore) Get(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - err := validOrganization(ctx) - if err != nil { - return nil, err - } - - d, err := s.store.Get(ctx, q) - if err != nil { - return nil, err - } - - if d.ID != s.organization { - return nil, chronograf.ErrOrganizationNotFound - } - - return d, nil -} - -// Update the organization in OrganizationsStore. -func (s *OrganizationsStore) Update(ctx context.Context, o *chronograf.Organization) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - _, err = s.store.Get(ctx, chronograf.OrganizationQuery{ID: &o.ID}) - if err != nil { - return err - } - - return s.store.Update(ctx, o) -} - -func (s *OrganizationsStore) CreateDefault(ctx context.Context) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - return s.store.CreateDefault(ctx) -} - -func (s *OrganizationsStore) DefaultOrganization(ctx context.Context) (*chronograf.Organization, error) { - err := validOrganization(ctx) - if err != nil { - return nil, err - } - - return s.store.DefaultOrganization(ctx) -} diff --git a/chronograf/organizations/organizations_test.go b/chronograf/organizations/organizations_test.go deleted file mode 100644 index cb28b1803a6..00000000000 --- a/chronograf/organizations/organizations_test.go +++ /dev/null @@ -1,346 +0,0 @@ -package organizations_test - -import ( - "context" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/organizations" -) - -// IgnoreFields is used because ID cannot be predicted reliably -// EquateEmpty is used because we want nil slices, arrays, and maps to be equal to the empty map -var organizationCmpOptions = cmp.Options{ - cmpopts.EquateEmpty(), - cmpopts.IgnoreFields(chronograf.Organization{}, "ID"), -} - -func TestOrganizations_All(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - } - type args struct { - organization string - } - tests := []struct { - name string - args args - fields fields - want []chronograf.Organization - wantErr bool - }{ - { - name: "No Organizations", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - AllF: func(ctx context.Context) ([]chronograf.Organization, error) { - return nil, fmt.Errorf("no Organizations") - }, - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "Default", - }, nil - }, - }, - }, - wantErr: true, - }, - { - name: "All Organizations", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "Default", - }, nil - }, - AllF: func(ctx context.Context) ([]chronograf.Organization, error) { - return []chronograf.Organization{ - { - Name: "howdy", - ID: "1337", - }, - { - Name: "doody", - ID: "1447", - }, - }, nil - }, - }, - }, - args: args{ - organization: "1337", - }, - want: []chronograf.Organization{ - { - Name: "howdy", - ID: "1337", - }, - { - Name: "Default", - ID: "0", - }, - }, - }, - } - for _, tt := range tests { - s := organizations.NewOrganizationsStore(tt.fields.OrganizationsStore, tt.args.organization) - ctx := context.WithValue(context.Background(), organizations.ContextKey, tt.args.organization) - gots, err := s.All(ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - for i, got := range gots { - if diff := cmp.Diff(got, tt.want[i], organizationCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.All():\n-got/+want\ndiff %s", tt.name, diff) - } - } - } -} - -func TestOrganizations_Add(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - } - type args struct { - organizationID string - ctx context.Context - organization *chronograf.Organization - } - tests := []struct { - name string - args args - fields fields - want *chronograf.Organization - wantErr bool - }{ - { - name: "Add Organization", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - AddF: func(ctx context.Context, s *chronograf.Organization) (*chronograf.Organization, error) { - return s, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1229", - Name: "howdy", - }, nil - }, - }, - }, - args: args{ - organizationID: "1229", - ctx: context.Background(), - organization: &chronograf.Organization{ - Name: "howdy", - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - s := organizations.NewOrganizationsStore(tt.fields.OrganizationsStore, tt.args.organizationID) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organizationID) - d, err := s.Add(tt.args.ctx, tt.args.organization) - if (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Add() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if tt.wantErr { - continue - } - got, err := s.Get(tt.args.ctx, chronograf.OrganizationQuery{ID: &d.ID}) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, tt.want, organizationCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.Add():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestOrganizations_Delete(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - } - type args struct { - organizationID string - ctx context.Context - organization *chronograf.Organization - } - tests := []struct { - name string - fields fields - args args - addFirst bool - wantErr bool - }{ - { - name: "Delete organization", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - DeleteF: func(ctx context.Context, s *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1229", - Name: "howdy", - }, nil - }, - }, - }, - args: args{ - organizationID: "1229", - ctx: context.Background(), - organization: &chronograf.Organization{ - ID: "1229", - Name: "howdy", - }, - }, - addFirst: true, - }, - } - for _, tt := range tests { - s := organizations.NewOrganizationsStore(tt.fields.OrganizationsStore, tt.args.organizationID) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organizationID) - err := s.Delete(tt.args.ctx, tt.args.organization) - if (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - } -} - -func TestOrganizations_Get(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - } - type args struct { - organizationID string - ctx context.Context - organization *chronograf.Organization - } - tests := []struct { - name string - fields fields - args args - want *chronograf.Organization - wantErr bool - }{ - { - name: "Get Organization", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "howdy", - }, nil - }, - }, - }, - args: args{ - organizationID: "1337", - ctx: context.Background(), - organization: &chronograf.Organization{ - ID: "1337", - Name: "howdy", - }, - }, - want: &chronograf.Organization{ - ID: "1337", - Name: "howdy", - }, - }, - } - for _, tt := range tests { - s := organizations.NewOrganizationsStore(tt.fields.OrganizationsStore, tt.args.organizationID) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organizationID) - got, err := s.Get(tt.args.ctx, chronograf.OrganizationQuery{ID: &tt.args.organization.ID}) - if (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if diff := cmp.Diff(got, tt.want, organizationCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestOrganizations_Update(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - } - type args struct { - organizationID string - ctx context.Context - organization *chronograf.Organization - name string - } - tests := []struct { - name string - fields fields - args args - want *chronograf.Organization - addFirst bool - wantErr bool - }{ - { - name: "Update Organization Name", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, s *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1229", - Name: "doody", - }, nil - }, - }, - }, - args: args{ - organizationID: "1229", - ctx: context.Background(), - organization: &chronograf.Organization{ - ID: "1229", - Name: "howdy", - }, - name: "doody", - }, - want: &chronograf.Organization{ - Name: "doody", - }, - addFirst: true, - }, - } - for _, tt := range tests { - if tt.args.name != "" { - tt.args.organization.Name = tt.args.name - } - s := organizations.NewOrganizationsStore(tt.fields.OrganizationsStore, tt.args.organizationID) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organizationID) - err := s.Update(tt.args.ctx, tt.args.organization) - if (err != nil) != tt.wantErr { - t.Errorf("%q. OrganizationsStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - got, err := s.Get(tt.args.ctx, chronograf.OrganizationQuery{ID: &tt.args.organization.ID}) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, tt.want, organizationCmpOptions...); diff != "" { - t.Errorf("%q. OrganizationsStore.Update():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} diff --git a/chronograf/organizations/servers.go b/chronograf/organizations/servers.go deleted file mode 100644 index 89bf1a8ed3d..00000000000 --- a/chronograf/organizations/servers.go +++ /dev/null @@ -1,111 +0,0 @@ -package organizations - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure that ServersStore implements chronograf.ServerStore -var _ chronograf.ServersStore = &ServersStore{} - -// ServersStore facade on a ServerStore that filters servers -// by organization. -type ServersStore struct { - store chronograf.ServersStore - organization string -} - -// NewServersStore creates a new ServersStore from an existing -// chronograf.ServerStore and an organization string -func NewServersStore(s chronograf.ServersStore, org string) *ServersStore { - return &ServersStore{ - store: s, - organization: org, - } -} - -// All retrieves all servers from the underlying ServerStore and filters them -// by organization. -func (s *ServersStore) All(ctx context.Context) ([]chronograf.Server, error) { - err := validOrganization(ctx) - if err != nil { - return nil, err - } - ds, err := s.store.All(ctx) - if err != nil { - return nil, err - } - - // This filters servers without allocating - // https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating - servers := ds[:0] - for _, d := range ds { - if d.Organization == s.organization { - servers = append(servers, d) - } - } - - return servers, nil -} - -// Add creates a new Server in the ServersStore with server.Organization set to be the -// organization from the server store. -func (s *ServersStore) Add(ctx context.Context, d chronograf.Server) (chronograf.Server, error) { - err := validOrganization(ctx) - if err != nil { - return chronograf.Server{}, err - } - - d.Organization = s.organization - return s.store.Add(ctx, d) -} - -// Delete the server from ServersStore -func (s *ServersStore) Delete(ctx context.Context, d chronograf.Server) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - d, err = s.store.Get(ctx, d.ID) - if err != nil { - return err - } - - return s.store.Delete(ctx, d) -} - -// Get returns a Server if the id exists and belongs to the organization that is set. -func (s *ServersStore) Get(ctx context.Context, id int) (chronograf.Server, error) { - err := validOrganization(ctx) - if err != nil { - return chronograf.Server{}, err - } - - d, err := s.store.Get(ctx, id) - if err != nil { - return chronograf.Server{}, err - } - - if d.Organization != s.organization { - return chronograf.Server{}, chronograf.ErrServerNotFound - } - - return d, nil -} - -// Update the server in ServersStore. -func (s *ServersStore) Update(ctx context.Context, d chronograf.Server) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - _, err = s.store.Get(ctx, d.ID) - if err != nil { - return err - } - - return s.store.Update(ctx, d) -} diff --git a/chronograf/organizations/servers_test.go b/chronograf/organizations/servers_test.go deleted file mode 100644 index 30f91e2c178..00000000000 --- a/chronograf/organizations/servers_test.go +++ /dev/null @@ -1,341 +0,0 @@ -package organizations_test - -import ( - "context" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/organizations" -) - -// IgnoreFields is used because ID cannot be predicted reliably -// EquateEmpty is used because we want nil slices, arrays, and maps to be equal to the empty map -var serverCmpOptions = cmp.Options{ - cmpopts.EquateEmpty(), - cmpopts.IgnoreFields(chronograf.Server{}, "ID"), - cmpopts.IgnoreFields(chronograf.Server{}, "Active"), -} - -func TestServers_All(t *testing.T) { - type fields struct { - ServersStore chronograf.ServersStore - } - type args struct { - organization string - } - tests := []struct { - name string - args args - fields fields - want []chronograf.Server - wantErr bool - }{ - { - name: "No Servers", - fields: fields{ - ServersStore: &mocks.ServersStore{ - AllF: func(ctx context.Context) ([]chronograf.Server, error) { - return nil, fmt.Errorf("no Servers") - }, - }, - }, - wantErr: true, - }, - { - name: "All Servers", - fields: fields{ - ServersStore: &mocks.ServersStore{ - AllF: func(ctx context.Context) ([]chronograf.Server, error) { - return []chronograf.Server{ - { - Name: "howdy", - Organization: "1337", - }, - { - Name: "doody", - Organization: "1338", - }, - }, nil - }, - }, - }, - args: args{ - organization: "1337", - }, - want: []chronograf.Server{ - { - Name: "howdy", - Organization: "1337", - }, - }, - }, - } - for _, tt := range tests { - s := organizations.NewServersStore(tt.fields.ServersStore, tt.args.organization) - ctx := context.WithValue(context.Background(), organizations.ContextKey, tt.args.organization) - gots, err := s.All(ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. ServersStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - for i, got := range gots { - if diff := cmp.Diff(got, tt.want[i], serverCmpOptions...); diff != "" { - t.Errorf("%q. ServersStore.All():\n-got/+want\ndiff %s", tt.name, diff) - } - } - } -} - -func TestServers_Add(t *testing.T) { - type fields struct { - ServersStore chronograf.ServersStore - } - type args struct { - organization string - ctx context.Context - server chronograf.Server - } - tests := []struct { - name string - args args - fields fields - want chronograf.Server - wantErr bool - }{ - { - name: "Add Server", - fields: fields{ - ServersStore: &mocks.ServersStore{ - AddF: func(ctx context.Context, s chronograf.Server) (chronograf.Server, error) { - return s, nil - }, - GetF: func(ctx context.Context, id int) (chronograf.Server, error) { - return chronograf.Server{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - server: chronograf.Server{ - ID: 1229, - Name: "howdy", - }, - }, - want: chronograf.Server{ - Name: "howdy", - Organization: "1337", - }, - }, - } - for _, tt := range tests { - s := organizations.NewServersStore(tt.fields.ServersStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - d, err := s.Add(tt.args.ctx, tt.args.server) - if (err != nil) != tt.wantErr { - t.Errorf("%q. ServersStore.Add() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - got, err := s.Get(tt.args.ctx, d.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, tt.want, serverCmpOptions...); diff != "" { - t.Errorf("%q. ServersStore.Add():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestServers_Delete(t *testing.T) { - type fields struct { - ServersStore chronograf.ServersStore - } - type args struct { - organization string - ctx context.Context - server chronograf.Server - } - tests := []struct { - name string - fields fields - args args - addFirst bool - wantErr bool - }{ - { - name: "Delete server", - fields: fields{ - ServersStore: &mocks.ServersStore{ - DeleteF: func(ctx context.Context, s chronograf.Server) error { - return nil - }, - GetF: func(ctx context.Context, id int) (chronograf.Server, error) { - return chronograf.Server{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - server: chronograf.Server{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - }, - addFirst: true, - }, - } - for _, tt := range tests { - s := organizations.NewServersStore(tt.fields.ServersStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - err := s.Delete(tt.args.ctx, tt.args.server) - if (err != nil) != tt.wantErr { - t.Errorf("%q. ServersStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - } -} - -func TestServers_Get(t *testing.T) { - type fields struct { - ServersStore chronograf.ServersStore - } - type args struct { - organization string - ctx context.Context - server chronograf.Server - } - tests := []struct { - name string - fields fields - args args - want chronograf.Server - wantErr bool - }{ - { - name: "Get Server", - fields: fields{ - ServersStore: &mocks.ServersStore{ - GetF: func(ctx context.Context, id int) (chronograf.Server, error) { - return chronograf.Server{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - server: chronograf.Server{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - }, - want: chronograf.Server{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - }, - } - for _, tt := range tests { - s := organizations.NewServersStore(tt.fields.ServersStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - got, err := s.Get(tt.args.ctx, tt.args.server.ID) - if (err != nil) != tt.wantErr { - t.Errorf("%q. ServersStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if diff := cmp.Diff(got, tt.want, serverCmpOptions...); diff != "" { - t.Errorf("%q. ServersStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestServers_Update(t *testing.T) { - type fields struct { - ServersStore chronograf.ServersStore - } - type args struct { - organization string - ctx context.Context - server chronograf.Server - name string - } - tests := []struct { - name string - fields fields - args args - want chronograf.Server - addFirst bool - wantErr bool - }{ - { - name: "Update Server Name", - fields: fields{ - ServersStore: &mocks.ServersStore{ - UpdateF: func(ctx context.Context, s chronograf.Server) error { - return nil - }, - GetF: func(ctx context.Context, id int) (chronograf.Server, error) { - return chronograf.Server{ - ID: 1229, - Name: "doody", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - server: chronograf.Server{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - name: "doody", - }, - want: chronograf.Server{ - Name: "doody", - Organization: "1337", - }, - addFirst: true, - }, - } - for _, tt := range tests { - if tt.args.name != "" { - tt.args.server.Name = tt.args.name - } - s := organizations.NewServersStore(tt.fields.ServersStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - err := s.Update(tt.args.ctx, tt.args.server) - if (err != nil) != tt.wantErr { - t.Errorf("%q. ServersStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - got, err := s.Get(tt.args.ctx, tt.args.server.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, tt.want, serverCmpOptions...); diff != "" { - t.Errorf("%q. ServersStore.Update():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} diff --git a/chronograf/organizations/sources.go b/chronograf/organizations/sources.go deleted file mode 100644 index eadc761146e..00000000000 --- a/chronograf/organizations/sources.go +++ /dev/null @@ -1,112 +0,0 @@ -package organizations - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// ensure that SourcesStore implements chronograf.SourceStore -var _ chronograf.SourcesStore = &SourcesStore{} - -// SourcesStore facade on a SourceStore that filters sources -// by organization. -type SourcesStore struct { - store chronograf.SourcesStore - organization string -} - -// NewSourcesStore creates a new SourcesStore from an existing -// chronograf.SourceStore and an organization string -func NewSourcesStore(s chronograf.SourcesStore, org string) *SourcesStore { - return &SourcesStore{ - store: s, - organization: org, - } -} - -// All retrieves all sources from the underlying SourceStore and filters them -// by organization. -func (s *SourcesStore) All(ctx context.Context) ([]chronograf.Source, error) { - err := validOrganization(ctx) - if err != nil { - return nil, err - } - - ds, err := s.store.All(ctx) - if err != nil { - return nil, err - } - - // This filters sources without allocating - // https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating - sources := ds[:0] - for _, d := range ds { - if d.Organization == s.organization { - sources = append(sources, d) - } - } - - return sources, nil -} - -// Add creates a new Source in the SourcesStore with source.Organization set to be the -// organization from the source store. -func (s *SourcesStore) Add(ctx context.Context, d chronograf.Source) (chronograf.Source, error) { - err := validOrganization(ctx) - if err != nil { - return chronograf.Source{}, err - } - - d.Organization = s.organization - return s.store.Add(ctx, d) -} - -// Delete the source from SourcesStore -func (s *SourcesStore) Delete(ctx context.Context, d chronograf.Source) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - d, err = s.store.Get(ctx, d.ID) - if err != nil { - return err - } - - return s.store.Delete(ctx, d) -} - -// Get returns a Source if the id exists and belongs to the organization that is set. -func (s *SourcesStore) Get(ctx context.Context, id int) (chronograf.Source, error) { - err := validOrganization(ctx) - if err != nil { - return chronograf.Source{}, err - } - - d, err := s.store.Get(ctx, id) - if err != nil { - return chronograf.Source{}, err - } - - if d.Organization != s.organization { - return chronograf.Source{}, chronograf.ErrSourceNotFound - } - - return d, nil -} - -// Update the source in SourcesStore. -func (s *SourcesStore) Update(ctx context.Context, d chronograf.Source) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - _, err = s.store.Get(ctx, d.ID) - if err != nil { - return err - } - - return s.store.Update(ctx, d) -} diff --git a/chronograf/organizations/sources_test.go b/chronograf/organizations/sources_test.go deleted file mode 100644 index 505104fa0ab..00000000000 --- a/chronograf/organizations/sources_test.go +++ /dev/null @@ -1,341 +0,0 @@ -package organizations_test - -import ( - "context" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/organizations" -) - -// IgnoreFields is used because ID cannot be predicted reliably -// EquateEmpty is used because we want nil slices, arrays, and maps to be equal to the empty map -var sourceCmpOptions = cmp.Options{ - cmpopts.EquateEmpty(), - cmpopts.IgnoreFields(chronograf.Source{}, "ID"), - cmpopts.IgnoreFields(chronograf.Source{}, "Default"), -} - -func TestSources_All(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - } - type args struct { - organization string - } - tests := []struct { - name string - args args - fields fields - want []chronograf.Source - wantErr bool - }{ - { - name: "No Sources", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return nil, fmt.Errorf("no Sources") - }, - }, - }, - wantErr: true, - }, - { - name: "All Sources", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{ - { - Name: "howdy", - Organization: "1337", - }, - { - Name: "doody", - Organization: "1338", - }, - }, nil - }, - }, - }, - args: args{ - organization: "1337", - }, - want: []chronograf.Source{ - { - Name: "howdy", - Organization: "1337", - }, - }, - }, - } - for _, tt := range tests { - s := organizations.NewSourcesStore(tt.fields.SourcesStore, tt.args.organization) - ctx := context.WithValue(context.Background(), organizations.ContextKey, tt.args.organization) - gots, err := s.All(ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. SourcesStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - for i, got := range gots { - if diff := cmp.Diff(got, tt.want[i], sourceCmpOptions...); diff != "" { - t.Errorf("%q. SourcesStore.All():\n-got/+want\ndiff %s", tt.name, diff) - } - } - } -} - -func TestSources_Add(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - } - type args struct { - organization string - ctx context.Context - source chronograf.Source - } - tests := []struct { - name string - args args - fields fields - want chronograf.Source - wantErr bool - }{ - { - name: "Add Source", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AddF: func(ctx context.Context, s chronograf.Source) (chronograf.Source, error) { - return s, nil - }, - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - source: chronograf.Source{ - ID: 1229, - Name: "howdy", - }, - }, - want: chronograf.Source{ - Name: "howdy", - Organization: "1337", - }, - }, - } - for _, tt := range tests { - s := organizations.NewSourcesStore(tt.fields.SourcesStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - d, err := s.Add(tt.args.ctx, tt.args.source) - if (err != nil) != tt.wantErr { - t.Errorf("%q. SourcesStore.Add() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - got, err := s.Get(tt.args.ctx, d.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, tt.want, sourceCmpOptions...); diff != "" { - t.Errorf("%q. SourcesStore.Add():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestSources_Delete(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - } - type args struct { - organization string - ctx context.Context - source chronograf.Source - } - tests := []struct { - name string - fields fields - args args - addFirst bool - wantErr bool - }{ - { - name: "Delete source", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - DeleteF: func(ctx context.Context, s chronograf.Source) error { - return nil - }, - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - source: chronograf.Source{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - }, - addFirst: true, - }, - } - for _, tt := range tests { - s := organizations.NewSourcesStore(tt.fields.SourcesStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - err := s.Delete(tt.args.ctx, tt.args.source) - if (err != nil) != tt.wantErr { - t.Errorf("%q. SourcesStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - } -} - -func TestSources_Get(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - } - type args struct { - organization string - ctx context.Context - source chronograf.Source - } - tests := []struct { - name string - fields fields - args args - want chronograf.Source - wantErr bool - }{ - { - name: "Get Source", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - source: chronograf.Source{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - }, - want: chronograf.Source{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - }, - } - for _, tt := range tests { - s := organizations.NewSourcesStore(tt.fields.SourcesStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - got, err := s.Get(tt.args.ctx, tt.args.source.ID) - if (err != nil) != tt.wantErr { - t.Errorf("%q. SourcesStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if diff := cmp.Diff(got, tt.want, sourceCmpOptions...); diff != "" { - t.Errorf("%q. SourcesStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestSources_Update(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - } - type args struct { - organization string - ctx context.Context - source chronograf.Source - name string - } - tests := []struct { - name string - fields fields - args args - want chronograf.Source - addFirst bool - wantErr bool - }{ - { - name: "Update Source Name", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - UpdateF: func(ctx context.Context, s chronograf.Source) error { - return nil - }, - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1229, - Name: "doody", - Organization: "1337", - }, nil - }, - }, - }, - args: args{ - organization: "1337", - ctx: context.Background(), - source: chronograf.Source{ - ID: 1229, - Name: "howdy", - Organization: "1337", - }, - name: "doody", - }, - want: chronograf.Source{ - Name: "doody", - Organization: "1337", - }, - addFirst: true, - }, - } - for _, tt := range tests { - if tt.args.name != "" { - tt.args.source.Name = tt.args.name - } - s := organizations.NewSourcesStore(tt.fields.SourcesStore, tt.args.organization) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.organization) - err := s.Update(tt.args.ctx, tt.args.source) - if (err != nil) != tt.wantErr { - t.Errorf("%q. SourcesStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - got, err := s.Get(tt.args.ctx, tt.args.source.ID) - if err != nil { - t.Fatal(err) - } - if diff := cmp.Diff(got, tt.want, sourceCmpOptions...); diff != "" { - t.Errorf("%q. SourcesStore.Update():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} diff --git a/chronograf/organizations/users.go b/chronograf/organizations/users.go deleted file mode 100644 index bb2a849e888..00000000000 --- a/chronograf/organizations/users.go +++ /dev/null @@ -1,284 +0,0 @@ -package organizations - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Ensure UsersStore implements chronograf.UsersStore. -var _ chronograf.UsersStore = &UsersStore{} - -// UsersStore facade on a UserStore that filters a users roles -// by organization. -// -// The high level idea here is to use the same underlying store for all users. -// In particular, this is done by having all the users Roles field be a set of -// all of the users roles in all organizations. Each CRUD method here takes care -// to ensure that the only roles that are modified are the roles for the organization -// that was provided on the UsersStore. -type UsersStore struct { - organization string - store chronograf.UsersStore -} - -// NewUsersStore creates a new UsersStore from an existing -// chronograf.UserStore and an organization string -func NewUsersStore(s chronograf.UsersStore, org string) *UsersStore { - return &UsersStore{ - store: s, - organization: org, - } -} - -// validOrganizationRoles ensures that each User Role has both an associated Organization and a Name -func validOrganizationRoles(orgID string, u *chronograf.User) error { - if u == nil || u.Roles == nil { - return nil - } - for _, r := range u.Roles { - if r.Organization == "" { - return fmt.Errorf("user role must have an Organization") - } - if r.Organization != orgID { - return fmt.Errorf("organizationID %s does not match %s", r.Organization, orgID) - } - if r.Name == "" { - return fmt.Errorf("user role must have a Name") - } - } - return nil -} - -// Get searches the UsersStore for using the query. -// The roles returned on the user are filtered to only contain roles that are for the organization -// specified on the organization store. -func (s *UsersStore) Get(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - err := validOrganization(ctx) - if err != nil { - return nil, err - } - - usr, err := s.store.Get(ctx, q) - if err != nil { - return nil, err - } - - // This filters a users roles so that the resulting struct only contains roles - // from the organization on the UsersStore. - roles := usr.Roles[:0] - for _, r := range usr.Roles { - if r.Organization == s.organization { - roles = append(roles, r) - } - } - - if len(roles) == 0 { - // This means that the user does not belong to the organization - // and therefore, is not found. - return nil, chronograf.ErrUserNotFound - } - - usr.Roles = roles - return usr, nil -} - -// Add creates a new User in the UsersStore. It validates that the user provided only -// has roles for the organization set on the UsersStore. -// If a user is not found in the underlying, it calls the underlying UsersStore Add method. -// If a user is found, it removes any existing roles a user has for an organization and appends -// the roles specified on the provided user and calls the uderlying UsersStore Update method. -func (s *UsersStore) Add(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - err := validOrganization(ctx) - if err != nil { - return nil, err - } - - // Validates that the users roles are only for the current organization. - if err := validOrganizationRoles(s.organization, u); err != nil { - return nil, err - } - - // retrieve the user from the underlying store - usr, err := s.store.Get(ctx, chronograf.UserQuery{ - Name: &u.Name, - Provider: &u.Provider, - Scheme: &u.Scheme, - }) - - switch err { - case nil: - // If there is no error continue to the rest of the code - break - case chronograf.ErrUserNotFound: - // If user is not found in the backed store, attempt to add the user - return s.store.Add(ctx, u) - default: - // return the error - return nil, err - } - - // Filter the retrieved users roles so that the resulting struct only contains roles - // that are not from the organization on the UsersStore. - roles := usr.Roles[:0] - for _, r := range usr.Roles { - if r.Organization != s.organization { - roles = append(roles, r) - } - } - - // If the user already has a role in the organization then the user - // cannot be "created". - // This can be thought of as: - // (total # of roles a user has) - (# of roles not in the organization) = (# of roles in organization) - // if this value is greater than 1 the user cannot be "added". - numRolesInOrganization := len(usr.Roles) - len(roles) - if numRolesInOrganization > 0 { - return nil, chronograf.ErrUserAlreadyExists - } - - // Set the users roles to be the union of the roles set on the provided user - // and the user that was found in the underlying store - usr.Roles = append(roles, u.Roles...) - - // u.SuperAdmin == true is logically equivalent to u.SuperAdmin, however - // it is more clear on a conceptual level to check equality - // - // TODO(desa): this should go away with https://github.com/influxdata/influxdb/chronograf/issues/2207 - // I do not like checking super admin here. The organization users store should only be - // concerned about organizations. - // - // If the user being added already existed in a previous organization, and was already a SuperAdmin, - // then this ensures that they retain their SuperAdmin status. And if they weren't a SuperAdmin, and - // the user being added has been granted SuperAdmin status, they will be promoted - if u.SuperAdmin { - usr.SuperAdmin = true - } - - // Update the user in the underlying store - if err := s.store.Update(ctx, usr); err != nil { - return nil, err - } - - // Return the provided user with ID set - u.ID = usr.ID - return u, nil -} - -// Delete a user from the UsersStore. This is done by stripping a user of -// any roles it has in the organization speicified on the UsersStore. -func (s *UsersStore) Delete(ctx context.Context, usr *chronograf.User) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - // retrieve the user from the underlying store - u, err := s.store.Get(ctx, chronograf.UserQuery{ID: &usr.ID}) - if err != nil { - return err - } - - // Filter the retrieved users roles so that the resulting slice contains - // roles that are not scoped to the organization provided - roles := u.Roles[:0] - for _, r := range u.Roles { - if r.Organization != s.organization { - roles = append(roles, r) - } - } - u.Roles = roles - return s.store.Update(ctx, u) -} - -// Update a user in the UsersStore. -func (s *UsersStore) Update(ctx context.Context, usr *chronograf.User) error { - err := validOrganization(ctx) - if err != nil { - return err - } - - // Validates that the users roles are only for the current organization. - if err := validOrganizationRoles(s.organization, usr); err != nil { - return err - } - - // retrieve the user from the underlying store - u, err := s.store.Get(ctx, chronograf.UserQuery{ID: &usr.ID}) - if err != nil { - return err - } - - // Filter the retrieved users roles so that the resulting slice contains - // roles that are not scoped to the organization provided - roles := u.Roles[:0] - for _, r := range u.Roles { - if r.Organization != s.organization { - roles = append(roles, r) - } - } - - // Make a copy of the usr so that we dont modify the underlying add roles on to - // the user that was passed in - user := *usr - - // Set the users roles to be the union of the roles set on the provided user - // and the user that was found in the underlying store - user.Roles = append(roles, usr.Roles...) - - return s.store.Update(ctx, &user) -} - -// All returns all users where roles have been filters to be exclusively for -// the organization provided on the UsersStore. -func (s *UsersStore) All(ctx context.Context) ([]chronograf.User, error) { - err := validOrganization(ctx) - if err != nil { - return nil, err - } - - // retrieve all users from the underlying UsersStore - usrs, err := s.store.All(ctx) - if err != nil { - return nil, err - } - - // Filter users to only contain users that have at least one role - // in the provided organization. - us := usrs[:0] - for _, usr := range usrs { - roles := usr.Roles[:0] - // This filters a users roles so that the resulting struct only contains roles - // from the organization on the UsersStore. - for _, r := range usr.Roles { - if r.Organization == s.organization { - roles = append(roles, r) - } - } - if len(roles) != 0 { - // Only add users if they have a role in the associated organization - usr.Roles = roles - us = append(us, usr) - } - } - - return us, nil -} - -// Num returns the number of users in the UsersStore -// This is unperformant, but should rarely be used. -func (s *UsersStore) Num(ctx context.Context) (int, error) { - err := validOrganization(ctx) - if err != nil { - return 0, err - } - - // retrieve all users from the underlying UsersStore - usrs, err := s.All(ctx) - if err != nil { - return 0, err - } - - return len(usrs), nil -} diff --git a/chronograf/organizations/users_test.go b/chronograf/organizations/users_test.go deleted file mode 100644 index c8494c6b55c..00000000000 --- a/chronograf/organizations/users_test.go +++ /dev/null @@ -1,1101 +0,0 @@ -package organizations_test - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/organizations" -) - -// IgnoreFields is used because ID cannot be predicted reliably -// EquateEmpty is used because we want nil slices, arrays, and maps to be equal to the empty map -var userCmpOptions = cmp.Options{ - cmpopts.IgnoreFields(chronograf.User{}, "ID"), - cmpopts.EquateEmpty(), -} - -func TestUsersStore_Get(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - } - type args struct { - ctx context.Context - userID uint64 - orgID string - } - tests := []struct { - name string - fields fields - args args - want *chronograf.User - wantErr bool - }{ - { - name: "Get user with no role in organization", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1234, - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "The HillBilliettas", - }, - }, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - userID: 1234, - orgID: "1336", - }, - wantErr: true, - }, - { - name: "Get user no organization set", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1234, - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "The HillBilliettas", - }, - }, - }, nil - }, - }, - }, - args: args{ - userID: 1234, - ctx: context.Background(), - }, - wantErr: true, - }, - { - name: "Get user scoped to an organization", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1234, - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "The HillBilliettas", - }, - { - Organization: "1336", - Name: "The BillHilliettos", - }, - }, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - userID: 1234, - orgID: "1336", - }, - want: &chronograf.User{ - Name: "billietta", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1336", - Name: "The BillHilliettos", - }, - }, - }, - }, - } - for _, tt := range tests { - s := organizations.NewUsersStore(tt.fields.UsersStore, tt.args.orgID) - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.orgID) - got, err := s.Get(tt.args.ctx, chronograf.UserQuery{ID: &tt.args.userID}) - if (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if diff := cmp.Diff(got, tt.want, userCmpOptions...); diff != "" { - t.Errorf("%q. UsersStore.Get():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestUsersStore_Add(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - } - type args struct { - ctx context.Context - u *chronograf.User - orgID string - } - tests := []struct { - name string - fields fields - args args - want *chronograf.User - wantErr bool - }{ - { - name: "Add new user - no org", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1336", - Name: "editor", - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Add new user", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return nil, chronograf.ErrUserNotFound - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1336", - Name: "editor", - }, - }, - }, - orgID: "1336", - }, - want: &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1336", - Name: "editor", - }, - }, - }, - }, - { - name: "Add non-new user without Role", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, - orgID: "1336", - }, - want: &chronograf.User{ - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, - }, - { - name: "Add non-new user with Role", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "editor", - }, - }, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1336", - Name: "admin", - }, - }, - }, - orgID: "1336", - }, - want: &chronograf.User{ - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1336", - Name: "admin", - }, - }, - }, - }, - { - name: "Add non-new user with Role. Stored user is not super admin. Provided user is super admin", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: false, - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "editor", - }, - }, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Organization: "1336", - Name: "admin", - }, - }, - }, - orgID: "1336", - }, - want: &chronograf.User{ - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Organization: "1336", - Name: "admin", - }, - }, - }, - }, - { - name: "Add user that already exists", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "editor", - }, - }, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - ID: 1234, - Name: "docbrown", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "admin", - }, - }, - }, - orgID: "1337", - }, - wantErr: true, - }, - { - name: "Has invalid Role: missing Organization", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return nil, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - orgID: "1338", - u: &chronograf.User{ - Name: "henrietta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "editor", - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Has invalid Role: missing Name", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return nil, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - orgID: "1337", - u: &chronograf.User{ - Name: "henrietta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - }, - }, - }, - }, - wantErr: true, - }, - { - name: "Has invalid Organization", - fields: fields{ - UsersStore: &mocks.UsersStore{}, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "henrietta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - chronograf.Role{}, - }, - }, - orgID: "1337", - }, - wantErr: true, - }, - { - name: "Organization does not match orgID", - fields: fields{ - UsersStore: &mocks.UsersStore{}, - }, - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "henrietta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "editor", - }, - }, - }, - orgID: "1337", - }, - wantErr: true, - }, - { - name: "Role Name not specified", - args: args{ - ctx: context.Background(), - u: &chronograf.User{ - Name: "henrietta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - }, - }, - }, - orgID: "1337", - }, - wantErr: true, - }, - } - for _, tt := range tests { - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.orgID) - s := organizations.NewUsersStore(tt.fields.UsersStore, tt.args.orgID) - - got, err := s.Add(tt.args.ctx, tt.args.u) - if (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Add() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got == nil && tt.want == nil { - continue - } - if diff := cmp.Diff(got, tt.want, userCmpOptions...); diff != "" { - t.Errorf("%q. UsersStore.Add():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestUsersStore_Delete(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - } - type args struct { - ctx context.Context - user *chronograf.User - orgID string - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "No such user", - fields: fields{ - UsersStore: &mocks.UsersStore{ - //AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - // return u, nil - //}, - //UpdateF: func(ctx context.Context, u *chronograf.User) error { - // return nil - //}, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return nil, chronograf.ErrUserNotFound - }, - }, - }, - args: args{ - ctx: context.Background(), - user: &chronograf.User{ - ID: 10, - }, - orgID: "1336", - }, - wantErr: true, - }, - { - name: "Derlete user", - fields: fields{ - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1234, - Name: "noone", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "The BillHilliettas", - }, - { - Organization: "1336", - Name: "The HillBilliettas", - }, - }, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - user: &chronograf.User{ - ID: 1234, - Name: "noone", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "The BillHilliettas", - }, - { - Organization: "1336", - Name: "The HillBilliettas", - }, - }, - }, - orgID: "1336", - }, - }, - } - for _, tt := range tests { - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.orgID) - s := organizations.NewUsersStore(tt.fields.UsersStore, tt.args.orgID) - if err := s.Delete(tt.args.ctx, tt.args.user); (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Delete() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - } -} - -func TestUsersStore_Update(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - } - type args struct { - ctx context.Context - usr *chronograf.User - roles []chronograf.Role - superAdmin bool - orgID string - } - tests := []struct { - name string - fields fields - args args - want *chronograf.User - wantErr bool - }{ - { - name: "No such user", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return nil, chronograf.ErrUserNotFound - }, - }, - }, - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - ID: 10, - }, - orgID: "1338", - }, - wantErr: true, - }, - { - name: "Update user role", - fields: fields{ - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "viewer", - }, - { - Organization: "1338", - Name: "editor", - }, - }, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, - roles: []chronograf.Role{ - { - Organization: "1338", - Name: "editor", - }, - }, - orgID: "1338", - }, - want: &chronograf.User{ - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "editor", - }, - }, - }, - }, - { - name: "Update user super admin", - fields: fields{ - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return &chronograf.User{ - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: false, - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "viewer", - }, - { - Organization: "1338", - Name: "editor", - }, - }, - }, nil - }, - }, - }, - args: args{ - ctx: context.Background(), - usr: &chronograf.User{ - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, - superAdmin: true, - orgID: "1338", - }, - want: &chronograf.User{ - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - }, - }, - } - for _, tt := range tests { - tt.args.ctx = context.WithValue(tt.args.ctx, organizations.ContextKey, tt.args.orgID) - s := organizations.NewUsersStore(tt.fields.UsersStore, tt.args.orgID) - - if tt.args.roles != nil { - tt.args.usr.Roles = tt.args.roles - } - - if tt.args.superAdmin { - tt.args.usr.SuperAdmin = tt.args.superAdmin - } - - if err := s.Update(tt.args.ctx, tt.args.usr); (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Update() error = %v, wantErr %v", tt.name, err, tt.wantErr) - } - - // for the empty test - if tt.want == nil { - continue - } - - if diff := cmp.Diff(tt.args.usr, tt.want, userCmpOptions...); diff != "" { - t.Errorf("%q. UsersStore.Update():\n-got/+want\ndiff %s", tt.name, diff) - } - - } -} - -func TestUsersStore_All(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - } - tests := []struct { - name string - fields fields - ctx context.Context - want []chronograf.User - wantRaw []chronograf.User - orgID string - wantErr bool - }{ - { - name: "No users", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AllF: func(ctx context.Context) ([]chronograf.User, error) { - return []chronograf.User{ - { - Name: "howdy", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "viewer", - }, - { - Organization: "1336", - Name: "viewer", - }, - }, - }, - { - Name: "doody2", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "editor", - }, - }, - }, - { - Name: "doody", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "editor", - }, - }, - }, - }, nil - }, - }, - }, - ctx: context.Background(), - orgID: "2330", - }, - { - name: "get all users", - orgID: "1338", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AllF: func(ctx context.Context) ([]chronograf.User, error) { - return []chronograf.User{ - { - Name: "howdy", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "viewer", - }, - { - Organization: "1336", - Name: "viewer", - }, - }, - }, - { - Name: "doody2", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "editor", - }, - }, - }, - { - Name: "doody", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "editor", - }, - }, - }, - }, nil - }, - }, - }, - ctx: context.Background(), - want: []chronograf.User{ - { - Name: "howdy", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "viewer", - }, - }, - }, - { - Name: "doody", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "editor", - }, - }, - }, - }, - }, - } - for _, tt := range tests { - tt.ctx = context.WithValue(tt.ctx, organizations.ContextKey, tt.orgID) - for _, u := range tt.wantRaw { - tt.fields.UsersStore.Add(tt.ctx, &u) - } - s := organizations.NewUsersStore(tt.fields.UsersStore, tt.orgID) - gots, err := s.All(tt.ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.All() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if diff := cmp.Diff(gots, tt.want, userCmpOptions...); diff != "" { - t.Errorf("%q. UsersStore.All():\n-got/+want\ndiff %s", tt.name, diff) - } - } -} - -func TestUsersStore_Num(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - } - tests := []struct { - name string - fields fields - ctx context.Context - orgID string - want int - wantErr bool - }{ - { - name: "No users", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AllF: func(ctx context.Context) ([]chronograf.User, error) { - return []chronograf.User{ - { - Name: "howdy", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "viewer", - }, - { - Organization: "1336", - Name: "viewer", - }, - }, - }, - { - Name: "doody2", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "editor", - }, - }, - }, - { - Name: "doody", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "editor", - }, - }, - }, - }, nil - }, - }, - }, - ctx: context.Background(), - orgID: "2330", - }, - { - name: "get all users", - orgID: "1338", - fields: fields{ - UsersStore: &mocks.UsersStore{ - AllF: func(ctx context.Context) ([]chronograf.User, error) { - return []chronograf.User{ - { - Name: "howdy", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "viewer", - }, - { - Organization: "1336", - Name: "viewer", - }, - }, - }, - { - Name: "doody2", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1337", - Name: "editor", - }, - }, - }, - { - Name: "doody", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Organization: "1338", - Name: "editor", - }, - }, - }, - }, nil - }, - }, - }, - ctx: context.Background(), - want: 2, - }, - } - for _, tt := range tests { - tt.ctx = context.WithValue(tt.ctx, organizations.ContextKey, tt.orgID) - s := organizations.NewUsersStore(tt.fields.UsersStore, tt.orgID) - got, err := s.Num(tt.ctx) - if (err != nil) != tt.wantErr { - t.Errorf("%q. UsersStore.Num() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if got != tt.want { - t.Errorf("%q. UsersStore.Num() = %d. want %d", tt.name, got, tt.want) - } - } -} diff --git a/chronograf/roles/roles.go b/chronograf/roles/roles.go deleted file mode 100644 index a5e63ad04ca..00000000000 --- a/chronograf/roles/roles.go +++ /dev/null @@ -1,66 +0,0 @@ -package roles - -import ( - "context" - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -type contextKey string - -// ContextKey is the key used to specify the -// role via context -const ContextKey = contextKey("role") - -func validRole(ctx context.Context) error { - // prevents panic in case of nil context - if ctx == nil { - return fmt.Errorf("expect non nil context") - } - role, ok := ctx.Value(ContextKey).(string) - // should never happen - if !ok { - return fmt.Errorf("expected role key to be a string") - } - switch role { - case MemberRoleName, ViewerRoleName, EditorRoleName, AdminRoleName: - return nil - default: - return fmt.Errorf("expected role key to be set") - } -} - -// Chronograf User Roles -const ( - MemberRoleName = "member" - ViewerRoleName = "viewer" - EditorRoleName = "editor" - AdminRoleName = "admin" - SuperAdminStatus = "superadmin" - - // Indicatior that the server should retrieve the default role for the organization. - WildcardRoleName = "*" -) - -var ( - // MemberRole is the role for a user who can only perform No operations. - MemberRole = chronograf.Role{ - Name: MemberRoleName, - } - - // ViewerRole is the role for a user who can only perform READ operations on Dashboards, Rules, Sources, and Servers, - ViewerRole = chronograf.Role{ - Name: ViewerRoleName, - } - - // EditorRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, and Servers. - EditorRole = chronograf.Role{ - Name: EditorRoleName, - } - - // AdminRole is the role for a user who can perform READ and WRITE operations on Dashboards, Rules, Sources, Servers, and Users - AdminRole = chronograf.Role{ - Name: AdminRoleName, - } -) diff --git a/chronograf/roles/sources.go b/chronograf/roles/sources.go deleted file mode 100644 index e45c74865ce..00000000000 --- a/chronograf/roles/sources.go +++ /dev/null @@ -1,143 +0,0 @@ -package roles - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// NOTE: -// This code is currently unused. however, it has been left in place because we anticipate -// that it may be used in the future. It was originally developed as a misunderstanding of -// https://github.com/influxdata/influxdb/chronograf/issues/1915 - -// ensure that SourcesStore implements chronograf.SourceStore -var _ chronograf.SourcesStore = &SourcesStore{} - -// SourcesStore facade on a SourceStore that filters sources -// by minimum role required to access the source. -// -// The role is passed around on the context and set when the -// SourcesStore is instantiated. -type SourcesStore struct { - store chronograf.SourcesStore - role string -} - -// NewSourcesStore creates a new SourcesStore from an existing -// chronograf.SourceStore and an role string -func NewSourcesStore(s chronograf.SourcesStore, role string) *SourcesStore { - return &SourcesStore{ - store: s, - role: role, - } -} - -// All retrieves all sources from the underlying SourceStore and filters them -// by role. -func (s *SourcesStore) All(ctx context.Context) ([]chronograf.Source, error) { - err := validRole(ctx) - if err != nil { - return nil, err - } - - ds, err := s.store.All(ctx) - if err != nil { - return nil, err - } - - // This filters sources without allocating - // https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating - sources := ds[:0] - for _, d := range ds { - if hasAuthorizedRole(d.Role, s.role) { - sources = append(sources, d) - } - } - - return sources, nil -} - -// Add creates a new Source in the SourcesStore with source.Role set to be the -// role from the source store. -func (s *SourcesStore) Add(ctx context.Context, d chronograf.Source) (chronograf.Source, error) { - err := validRole(ctx) - if err != nil { - return chronograf.Source{}, err - } - - return s.store.Add(ctx, d) -} - -// Delete the source from SourcesStore -func (s *SourcesStore) Delete(ctx context.Context, d chronograf.Source) error { - err := validRole(ctx) - if err != nil { - return err - } - - d, err = s.store.Get(ctx, d.ID) - if err != nil { - return err - } - - return s.store.Delete(ctx, d) -} - -// Get returns a Source if the id exists and belongs to the role that is set. -func (s *SourcesStore) Get(ctx context.Context, id int) (chronograf.Source, error) { - err := validRole(ctx) - if err != nil { - return chronograf.Source{}, err - } - - d, err := s.store.Get(ctx, id) - if err != nil { - return chronograf.Source{}, err - } - - if !hasAuthorizedRole(d.Role, s.role) { - return chronograf.Source{}, chronograf.ErrSourceNotFound - } - - return d, nil -} - -// Update the source in SourcesStore. -func (s *SourcesStore) Update(ctx context.Context, d chronograf.Source) error { - err := validRole(ctx) - if err != nil { - return err - } - - _, err = s.store.Get(ctx, d.ID) - if err != nil { - return err - } - - return s.store.Update(ctx, d) -} - -// hasAuthorizedRole checks that the role provided has at least -// the minimum role required. -func hasAuthorizedRole(sourceRole, providedRole string) bool { - switch sourceRole { - case ViewerRoleName: - switch providedRole { - case ViewerRoleName, EditorRoleName, AdminRoleName: - return true - } - case EditorRoleName: - switch providedRole { - case EditorRoleName, AdminRoleName: - return true - } - case AdminRoleName: - switch providedRole { - case AdminRoleName: - return true - } - } - - return false -} diff --git a/chronograf/roles/sources_test.go b/chronograf/roles/sources_test.go deleted file mode 100644 index 9a69718b3f2..00000000000 --- a/chronograf/roles/sources_test.go +++ /dev/null @@ -1,489 +0,0 @@ -package roles - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -func TestSources_Get(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - } - type args struct { - role string - id int - } - type wants struct { - source chronograf.Source - err bool - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "Get viewer source as viewer", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, nil - }, - }, - }, - args: args{ - role: "viewer", - id: 1, - }, - wants: wants{ - source: chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, - }, - }, - { - name: "Get viewer source as editor", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, nil - }, - }, - }, - args: args{ - role: "editor", - id: 1, - }, - wants: wants{ - source: chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, - }, - }, - { - name: "Get viewer source as admin", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, nil - }, - }, - }, - args: args{ - role: "admin", - id: 1, - }, - wants: wants{ - source: chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, - }, - }, - { - name: "Get editor source as editor", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, nil - }, - }, - }, - args: args{ - role: "editor", - id: 1, - }, - wants: wants{ - source: chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, - }, - }, - { - name: "Get editor source as admin", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, nil - }, - }, - }, - args: args{ - role: "admin", - id: 1, - }, - wants: wants{ - source: chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, - }, - }, - { - name: "Get editor source as viewer - want error", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, nil - }, - }, - }, - args: args{ - role: "viewer", - id: 1, - }, - wants: wants{ - err: true, - }, - }, - { - name: "Get admin source as admin", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "admin", - }, nil - }, - }, - }, - args: args{ - role: "admin", - id: 1, - }, - wants: wants{ - source: chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "admin", - }, - }, - }, - { - name: "Get admin source as viewer - want error", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "admin", - }, nil - }, - }, - }, - args: args{ - role: "viewer", - id: 1, - }, - wants: wants{ - err: true, - }, - }, - { - name: "Get admin source as editor - want error", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "admin", - }, nil - }, - }, - }, - args: args{ - role: "editor", - id: 1, - }, - wants: wants{ - err: true, - }, - }, - { - name: "Get source bad context", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "admin", - }, nil - }, - }, - }, - args: args{ - role: "random role", - id: 1, - }, - wants: wants{ - err: true, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - store := NewSourcesStore(tt.fields.SourcesStore, tt.args.role) - - ctx := context.Background() - - if tt.args.role != "" { - ctx = context.WithValue(ctx, ContextKey, tt.args.role) - } - - source, err := store.Get(ctx, tt.args.id) - if (err != nil) != tt.wants.err { - t.Errorf("%q. Store.Sources().Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) - return - } - if diff := cmp.Diff(source, tt.wants.source); diff != "" { - t.Errorf("%q. Store.Sources().Get():\n-got/+want\ndiff %s", tt.name, diff) - } - }) - } -} - -func TestSources_All(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - } - type args struct { - role string - } - type wants struct { - sources []chronograf.Source - err bool - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "Get viewer sources as viewer", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, - { - ID: 2, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, - { - ID: 3, - Name: "my sweet name", - Organization: "0", - Role: "admin", - }, - }, nil - }, - }, - }, - args: args{ - role: "viewer", - }, - wants: wants{ - sources: []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, - }, - }, - }, - { - name: "Get editor sources as editor", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, - { - ID: 2, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, - { - ID: 3, - Name: "my sweet name", - Organization: "0", - Role: "admin", - }, - }, nil - }, - }, - }, - args: args{ - role: "editor", - }, - wants: wants{ - sources: []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, - { - ID: 2, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, - }, - }, - }, - { - name: "Get admin sources as admin", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, - { - ID: 2, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, - { - ID: 3, - Name: "my sweet name", - Organization: "0", - Role: "admin", - }, - }, nil - }, - }, - }, - args: args{ - role: "admin", - }, - wants: wants{ - sources: []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - Role: "viewer", - }, - { - ID: 2, - Name: "my sweet name", - Organization: "0", - Role: "editor", - }, - { - ID: 3, - Name: "my sweet name", - Organization: "0", - Role: "admin", - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - store := NewSourcesStore(tt.fields.SourcesStore, tt.args.role) - - ctx := context.Background() - - if tt.args.role != "" { - ctx = context.WithValue(ctx, ContextKey, tt.args.role) - } - - sources, err := store.All(ctx) - if (err != nil) != tt.wants.err { - t.Errorf("%q. Store.Sources().Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) - return - } - if diff := cmp.Diff(sources, tt.wants.sources); diff != "" { - t.Errorf("%q. Store.Sources().Get():\n-got/+want\ndiff %s", tt.name, diff) - } - }) - } -} diff --git a/chronograf/server/Makefile b/chronograf/server/Makefile deleted file mode 100644 index 1ebe969917a..00000000000 --- a/chronograf/server/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# List any generated files here -TARGETS = swagger_gen.go -# List any source files used to generate the targets here -SOURCES = swagger.json swagger.go -# List any directories that have their own Makefile here -SUBDIRS = - -# Default target -all: $(SUBDIRS) $(TARGETS) - -# Recurse into subdirs for same make goal -$(SUBDIRS): - $(MAKE) -C $@ $(MAKECMDGOALS) - -# Clean all targets recursively -clean: $(SUBDIRS) - rm -f $(TARGETS) - -# Define go generate if not already defined -GO_GENERATE := go generate - -# Run go generate for the targets -$(TARGETS): $(SOURCES) - $(GO_GENERATE) -x - -.PHONY: all clean $(SUBDIRS) diff --git a/chronograf/server/TODO.go b/chronograf/server/TODO.go deleted file mode 100644 index 242f0f8e479..00000000000 --- a/chronograf/server/TODO.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build !assets - -package server - -import ( - "errors" -) - -// The functions defined in this file are placeholders when the binary is compiled -// without assets. - -// Asset returns an error stating no assets were included in the binary. -func Asset(string) ([]byte, error) { - return nil, errors.New("no assets included in binary") -} diff --git a/chronograf/server/annotations.go b/chronograf/server/annotations.go deleted file mode 100644 index 262ff80fd1c..00000000000 --- a/chronograf/server/annotations.go +++ /dev/null @@ -1,452 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/influx" -) - -const ( - since = "since" - until = "until" - timeMilliFormat = "2006-01-02T15:04:05.999Z07:00" -) - -type annotationLinks struct { - Self string `json:"self"` // Self link mapping to this resource -} - -type annotationResponse struct { - ID string `json:"id"` // ID is the unique annotation identifier - StartTime string `json:"startTime"` // StartTime in RFC3339 of the start of the annotation - EndTime string `json:"endTime"` // EndTime in RFC3339 of the end of the annotation - Text string `json:"text"` // Text is the associated user-facing text describing the annotation - Type string `json:"type"` // Type describes the kind of annotation - Links annotationLinks `json:"links"` -} - -func newAnnotationResponse(src chronograf.Source, a *chronograf.Annotation) annotationResponse { - base := "/chronograf/v1/sources" - res := annotationResponse{ - ID: a.ID, - StartTime: a.StartTime.UTC().Format(timeMilliFormat), - EndTime: a.EndTime.UTC().Format(timeMilliFormat), - Text: a.Text, - Type: a.Type, - Links: annotationLinks{ - Self: fmt.Sprintf("%s/%d/annotations/%s", base, src.ID, a.ID), - }, - } - - if a.EndTime.IsZero() { - res.EndTime = "" - } - - return res -} - -type annotationsResponse struct { - Annotations []annotationResponse `json:"annotations"` -} - -func newAnnotationsResponse(src chronograf.Source, as []chronograf.Annotation) annotationsResponse { - annotations := make([]annotationResponse, len(as)) - for i, a := range as { - annotations[i] = newAnnotationResponse(src, &a) - } - return annotationsResponse{ - Annotations: annotations, - } -} - -func validAnnotationQuery(query url.Values) (startTime, stopTime time.Time, err error) { - start := query.Get(since) - if start == "" { - return time.Time{}, time.Time{}, fmt.Errorf("since parameter is required") - } - - startTime, err = time.Parse(timeMilliFormat, start) - if err != nil { - return - } - - // if until isn't stated, the default time is now - stopTime = time.Now() - stop := query.Get(until) - if stop != "" { - stopTime, err = time.Parse(timeMilliFormat, stop) - if err != nil { - return time.Time{}, time.Time{}, err - } - } - if startTime.After(stopTime) { - startTime, stopTime = stopTime, startTime - } - return startTime, stopTime, nil -} - -// Annotations returns all annotations within the annotations store -func (s *Service) Annotations(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - start, stop, err := validAnnotationQuery(r.URL.Query()) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - ts, err := s.TimeSeries(src) - if err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - if err = ts.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - store := influx.NewAnnotationStore(ts) - annotations, err := store.All(ctx, start, stop) - if err != nil { - msg := fmt.Errorf("error loading annotations: %v", err) - unknownErrorWithMessage(w, msg, s.Logger) - return - } - - res := newAnnotationsResponse(src, annotations) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// Annotation returns a specified annotation id within the annotations store -func (s *Service) Annotation(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - annoID, err := paramStr("aid", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - ts, err := s.TimeSeries(src) - if err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - if err = ts.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - store := influx.NewAnnotationStore(ts) - anno, err := store.Get(ctx, annoID) - if err != nil { - if err != chronograf.ErrAnnotationNotFound { - msg := fmt.Errorf("error loading annotation: %v", err) - unknownErrorWithMessage(w, msg, s.Logger) - return - } - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newAnnotationResponse(src, anno) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -type newAnnotationRequest struct { - StartTime time.Time - EndTime time.Time - Text string `json:"text,omitempty"` // Text is the associated user-facing text describing the annotation - Type string `json:"type,omitempty"` // Type describes the kind of annotation -} - -func (ar *newAnnotationRequest) UnmarshalJSON(data []byte) error { - type Alias newAnnotationRequest - aux := &struct { - StartTime string `json:"startTime"` // StartTime is the time in rfc3339 milliseconds - EndTime string `json:"endTime"` // EndTime is the time in rfc3339 milliseconds - *Alias - }{ - Alias: (*Alias)(ar), - } - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - - var err error - ar.StartTime, err = time.Parse(timeMilliFormat, aux.StartTime) - if err != nil { - return err - } - - ar.EndTime, err = time.Parse(timeMilliFormat, aux.EndTime) - if err != nil { - return err - } - - if ar.StartTime.After(ar.EndTime) { - ar.StartTime, ar.EndTime = ar.EndTime, ar.StartTime - } - - return nil -} - -func (ar *newAnnotationRequest) Annotation() *chronograf.Annotation { - return &chronograf.Annotation{ - StartTime: ar.StartTime, - EndTime: ar.EndTime, - Text: ar.Text, - Type: ar.Type, - } -} - -// NewAnnotation adds the annotation from a POST body to the annotations store -func (s *Service) NewAnnotation(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - ts, err := s.TimeSeries(src) - if err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - if err = ts.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - var req newAnnotationRequest - if err = json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - store := influx.NewAnnotationStore(ts) - anno, err := store.Add(ctx, req.Annotation()) - if err != nil { - if err == chronograf.ErrUpstreamTimeout { - msg := "Timeout waiting for response" - Error(w, http.StatusRequestTimeout, msg, s.Logger) - return - } - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newAnnotationResponse(src, anno) - location(w, res.Links.Self) - encodeJSON(w, http.StatusCreated, res, s.Logger) -} - -// RemoveAnnotation removes the annotation from the time series source -func (s *Service) RemoveAnnotation(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - annoID, err := paramStr("aid", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - ts, err := s.TimeSeries(src) - if err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - if err = ts.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - store := influx.NewAnnotationStore(ts) - if err = store.Delete(ctx, annoID); err != nil { - if err == chronograf.ErrUpstreamTimeout { - msg := "Timeout waiting for response" - Error(w, http.StatusRequestTimeout, msg, s.Logger) - return - } - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -type updateAnnotationRequest struct { - StartTime *time.Time `json:"startTime,omitempty"` // StartTime is the time in rfc3339 milliseconds - EndTime *time.Time `json:"endTime,omitempty"` // EndTime is the time in rfc3339 milliseconds - Text *string `json:"text,omitempty"` // Text is the associated user-facing text describing the annotation - Type *string `json:"type,omitempty"` // Type describes the kind of annotation -} - -// TODO: make sure that endtime is after starttime -func (u *updateAnnotationRequest) UnmarshalJSON(data []byte) error { - type Alias updateAnnotationRequest - aux := &struct { - StartTime *string `json:"startTime,omitempty"` - EndTime *string `json:"endTime,omitempty"` - *Alias - }{ - Alias: (*Alias)(u), - } - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - - if aux.StartTime != nil { - tm, err := time.Parse(timeMilliFormat, *aux.StartTime) - if err != nil { - return err - } - u.StartTime = &tm - } - - if aux.EndTime != nil { - tm, err := time.Parse(timeMilliFormat, *aux.EndTime) - if err != nil { - return err - } - u.EndTime = &tm - } - - // Update must have at least one field set - if u.StartTime == nil && u.EndTime == nil && u.Text == nil && u.Type == nil { - return fmt.Errorf("update request must have at least one field") - } - - return nil -} - -// UpdateAnnotation overwrite an existing annotation -func (s *Service) UpdateAnnotation(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - annoID, err := paramStr("aid", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - ts, err := s.TimeSeries(src) - if err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - if err = ts.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - store := influx.NewAnnotationStore(ts) - cur, err := store.Get(ctx, annoID) - if err != nil { - notFound(w, annoID, s.Logger) - return - } - - var req updateAnnotationRequest - if err = json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - if req.StartTime != nil { - cur.StartTime = *req.StartTime - } - if req.EndTime != nil { - cur.EndTime = *req.EndTime - } - if req.Text != nil { - cur.Text = *req.Text - } - if req.Type != nil { - cur.Type = *req.Type - } - - if err = store.Update(ctx, cur); err != nil { - if err == chronograf.ErrUpstreamTimeout { - msg := "Timeout waiting for response" - Error(w, http.StatusRequestTimeout, msg, s.Logger) - return - } - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newAnnotationResponse(src, cur) - location(w, res.Links.Self) - encodeJSON(w, http.StatusOK, res, s.Logger) -} diff --git a/chronograf/server/annotations_test.go b/chronograf/server/annotations_test.go deleted file mode 100644 index 23984b4139a..00000000000 --- a/chronograf/server/annotations_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package server - -import ( - "bytes" - "context" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -func TestService_Annotations(t *testing.T) { - type fields struct { - Store DataStore - TimeSeriesClient TimeSeriesClient - } - - tests := []struct { - name string - fields fields - w *httptest.ResponseRecorder - r *http.Request - ID string - want string - }{ - { - name: "error no id", - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "/chronograf/v1/sources/1/annotations", bytes.NewReader([]byte(`howdy`))), - want: `{"code":422,"message":"error converting ID "}`, - }, - { - name: "no since parameter", - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "/chronograf/v1/sources/1/annotations", bytes.NewReader([]byte(`howdy`))), - want: `{"code":422,"message":"since parameter is required"}`, - }, - { - name: "invalid since parameter", - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "/chronograf/v1/sources/1/annotations?since=howdy", bytes.NewReader([]byte(`howdy`))), - want: `{"code":422,"message":"parsing time \"howdy\" as \"2006-01-02T15:04:05.999Z07:00\": cannot parse \"howdy\" as \"2006\""}`, - }, - { - name: "error is returned when get is an error", - fields: fields{ - Store: &mocks.Store{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{}, fmt.Errorf("error") - }, - }, - }, - }, - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "/chronograf/v1/sources/1/annotations?since=1985-04-12T23:20:50.52Z", bytes.NewReader([]byte(`howdy`))), - want: `{"code":404,"message":"ID 1 not found"}`, - }, - { - name: "error is returned connect is an error", - fields: fields{ - Store: &mocks.Store{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: ID, - }, nil - }, - }, - }, - TimeSeriesClient: &mocks.TimeSeries{ - ConnectF: func(context.Context, *chronograf.Source) error { - return fmt.Errorf("error)") - }, - }, - }, - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "/chronograf/v1/sources/1/annotations?since=1985-04-12T23:20:50.52Z", bytes.NewReader([]byte(`howdy`))), - want: `{"code":400,"message":"unable to connect to source 1: error)"}`, - }, - { - name: "error returned when annotations are invalid", - fields: fields{ - Store: &mocks.Store{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: ID, - }, nil - }, - }, - }, - TimeSeriesClient: &mocks.TimeSeries{ - ConnectF: func(context.Context, *chronograf.Source) error { - return nil - }, - QueryF: func(context.Context, chronograf.Query) (chronograf.Response, error) { - return mocks.NewResponse(`{[]}`, nil), nil - }, - }, - }, - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "/chronograf/v1/sources/1/annotations?since=1985-04-12T23:20:50.52Z", bytes.NewReader([]byte(`howdy`))), - want: `{"code":500,"message":"unknown error: error loading annotations: invalid character '[' looking for beginning of object key string"}`, - }, - { - name: "error is returned connect is an error", - fields: fields{ - Store: &mocks.Store{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: ID, - }, nil - }, - }, - }, - TimeSeriesClient: &mocks.TimeSeries{ - ConnectF: func(context.Context, *chronograf.Source) error { - return nil - }, - QueryF: func(context.Context, chronograf.Query) (chronograf.Response, error) { - return mocks.NewResponse(`[ - { - "series": [ - { - "name": "annotations", - "columns": [ - "time", - "start_time", - "modified_time_ns", - "text", - "type", - "id" - ], - "values": [ - [ - 1516920177345000000, - 0, - 1516989242129417403, - "mytext", - "mytype", - "ea0aa94b-969a-4cd5-912a-5db61d502268" - ] - ] - } - ] - } - ]`, nil), nil - }, - }, - }, - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "/chronograf/v1/sources/1/annotations?since=1985-04-12T23:20:50.52Z", bytes.NewReader([]byte(`howdy`))), - want: `{"annotations":[{"id":"ea0aa94b-969a-4cd5-912a-5db61d502268","startTime":"1970-01-01T00:00:00Z","endTime":"2018-01-25T22:42:57.345Z","text":"mytext","type":"mytype","links":{"self":"/chronograf/v1/sources/1/annotations/ea0aa94b-969a-4cd5-912a-5db61d502268"}}]} -`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.r = tt.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - s := &Service{ - Store: tt.fields.Store, - TimeSeriesClient: tt.fields.TimeSeriesClient, - Logger: mocks.NewLogger(), - } - s.Annotations(tt.w, tt.r) - got := tt.w.Body.String() - if got != tt.want { - t.Errorf("Annotations() got != want:\n%s\n%s", got, tt.want) - } - }) - } -} diff --git a/chronograf/server/assets.go b/chronograf/server/assets.go deleted file mode 100644 index 16f0ded8bd8..00000000000 --- a/chronograf/server/assets.go +++ /dev/null @@ -1,58 +0,0 @@ -package server - -import ( - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/dist" -) - -const ( - // Dir is prefix of the assets in the bindata - Dir = "../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" - // DefaultContentType is the content-type to return for the Default file - DefaultContentType = "text/html; charset=utf-8" -) - -// AssetsOpts configures the asset middleware -type AssetsOpts struct { - // Develop when true serves assets from ui/build directory directly; false will use internal bindata. - Develop bool - // Logger will log the asset served - Logger chronograf.Logger -} - -// Assets creates a middleware that will serve a single page app. -func Assets(opts AssetsOpts) http.Handler { - var assets chronograf.Assets - if opts.Develop { - assets = &dist.DebugAssets{ - Dir: DebugDir, - Default: DebugDefault, - } - } else { - assets = &dist.BindataAssets{ - Prefix: Dir, - Default: Default, - DefaultContentType: DefaultContentType, - } - } - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if opts.Logger != nil { - opts.Logger. - WithField("component", "server"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("url", r.URL). - Info("Serving assets") - } - assets.Handler().ServeHTTP(w, r) - }) -} diff --git a/chronograf/server/auth.go b/chronograf/server/auth.go deleted file mode 100644 index fa38e746226..00000000000 --- a/chronograf/server/auth.go +++ /dev/null @@ -1,256 +0,0 @@ -package server - -import ( - "context" - "fmt" - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" - "github.com/influxdata/influxdb/v2/chronograf/organizations" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -// HasAuthorizedToken extracts the token from a request and validates it using the authenticator. -// It is used by routes that need access to the token to populate links request. -func HasAuthorizedToken(auth oauth2.Authenticator, r *http.Request) (oauth2.Principal, error) { - ctx := r.Context() - return auth.Validate(ctx, r) -} - -// AuthorizedToken extracts the token and validates; if valid the next handler -// will be run. The principal will be sent to the next handler via the request's -// Context. It is up to the next handler to determine if the principal has access. -// On failure, will return http.StatusForbidden. -func AuthorizedToken(auth oauth2.Authenticator, logger chronograf.Logger, next http.Handler) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - log := logger. - WithField("component", "token_auth"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("url", r.URL) - - ctx := r.Context() - // We do not check the authorization of the principal. Those - // served further down the chain should do so. - principal, err := auth.Validate(ctx, r) - if err != nil { - log.Error("Invalid principal") - w.WriteHeader(http.StatusForbidden) - return - } - - // If the principal is valid we will extend its lifespan - // into the future - principal, err = auth.Extend(ctx, w, principal) - if err != nil { - log.Error("Unable to extend principal") - w.WriteHeader(http.StatusForbidden) - return - } - - // Send the principal to the next handler - ctx = context.WithValue(ctx, oauth2.PrincipalKey, principal) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -// RawStoreAccess gives a super admin access to the data store without a facade. -func RawStoreAccess(logger chronograf.Logger, next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - if isServer := hasServerContext(ctx); isServer { - next(w, r) - return - } - - log := logger. - WithField("component", "raw_store"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("url", r.URL) - - if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin { - r = r.WithContext(serverContext(ctx)) - } else { - log.Error("User making request is not a SuperAdmin") - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - - next(w, r) - } -} - -// AuthorizedUser extracts the user name and provider from context. If the -// user and provider can be found on the context, we look up the user by their -// name and provider. If the user is found, we verify that the user has at at -// least the role supplied. -func AuthorizedUser( - store DataStore, - useAuth bool, - role string, - logger chronograf.Logger, - next http.HandlerFunc, -) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - serverCtx := serverContext(ctx) - - log := logger. - WithField("component", "role_auth"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("url", r.URL) - - defaultOrg, err := store.Organizations(serverCtx).DefaultOrganization(serverCtx) - if err != nil { - log.Error(fmt.Sprintf("Failed to retrieve the default organization: %v", err)) - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - - if !useAuth { - // If there is no auth, then set the organization id to be the default org id on context - // so that calls like hasOrganizationContext as used in Organization Config service - // method OrganizationConfig can successfully get the organization id - ctx = context.WithValue(ctx, organizations.ContextKey, defaultOrg.ID) - - // And if there is no auth, then give the user raw access to the DataStore - r = r.WithContext(serverContext(ctx)) - next(w, r) - return - } - - p, err := getValidPrincipal(ctx) - if err != nil { - log.Error("Failed to retrieve principal from context") - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - scheme, err := getScheme(ctx) - if err != nil { - log.Error("Failed to retrieve scheme from context") - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - - // This is as if the user was logged into the default organization - if p.Organization == "" { - p.Organization = defaultOrg.ID - } - - // validate that the organization exists - _, err = store.Organizations(serverCtx).Get(serverCtx, chronograf.OrganizationQuery{ID: &p.Organization}) - if err != nil { - log.Error(fmt.Sprintf("Failed to retrieve organization %s from organizations store", p.Organization)) - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - ctx = context.WithValue(ctx, organizations.ContextKey, p.Organization) - // TODO: seems silly to look up a user twice - u, err := store.Users(serverCtx).Get(serverCtx, chronograf.UserQuery{ - Name: &p.Subject, - Provider: &p.Issuer, - Scheme: &scheme, - }) - - if err != nil { - log.Error("Failed to retrieve user") - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - // In particular this is used by sever/users.go so that we know when and when not to - // allow users to make someone a super admin - ctx = context.WithValue(ctx, UserContextKey, u) - - if u.SuperAdmin { - // To access resources (servers, sources, databases, layouts) within a DataStore, - // an organization and a role are required even if you are a super admin or are - // not using auth. Every user's current organization is set on context to filter - // the resources accessed within a DataStore, including for super admin or when - // not using auth. In this way, a DataStore can treat all requests the same, - // including those from a super admin and when not using auth. - // - // As for roles, in the case of super admin or when not using auth, the user's - // role on context (though not on their JWT or user) is set to be admin. In order - // to access all resources belonging to their current organization. - ctx = context.WithValue(ctx, roles.ContextKey, roles.AdminRoleName) - r = r.WithContext(ctx) - next(w, r) - return - } - - u, err = store.Users(ctx).Get(ctx, chronograf.UserQuery{ - Name: &p.Subject, - Provider: &p.Issuer, - Scheme: &scheme, - }) - if err != nil { - log.Error("Failed to retrieve user") - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - - if hasAuthorizedRole(u, role) { - if len(u.Roles) != 1 { - msg := `User %d has too many role in organization. User: %#v.Please report this log at https://github.com/influxdata/influxdb/chronograf/issues/new"` - log.Error(fmt.Sprint(msg, u.ID, u)) - unknownErrorWithMessage(w, fmt.Errorf("please have administrator check logs and report error"), logger) - return - } - // use the first role, since there should only ever be one - // for any particular organization and hasAuthorizedRole - // should ensure that at least one role for the org exists - ctx = context.WithValue(ctx, roles.ContextKey, u.Roles[0].Name) - r = r.WithContext(ctx) - next(w, r) - return - } - - Error(w, http.StatusForbidden, "User is not authorized", logger) - }) -} - -func hasAuthorizedRole(u *chronograf.User, role string) bool { - if u == nil { - return false - } - - switch role { - case roles.MemberRoleName: - for _, r := range u.Roles { - switch r.Name { - case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: - return true - } - } - case roles.ViewerRoleName: - for _, r := range u.Roles { - switch r.Name { - case roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: - return true - } - } - case roles.EditorRoleName: - for _, r := range u.Roles { - switch r.Name { - case roles.EditorRoleName, roles.AdminRoleName: - return true - } - } - case roles.AdminRoleName: - for _, r := range u.Roles { - switch r.Name { - case roles.AdminRoleName: - return true - } - } - case roles.SuperAdminStatus: - // SuperAdmins should have been authorized before this. - // This is only meant to restrict access for non-superadmins. - return false - } - - return false -} diff --git a/chronograf/server/auth_test.go b/chronograf/server/auth_test.go deleted file mode 100644 index 307517d9326..00000000000 --- a/chronograf/server/auth_test.go +++ /dev/null @@ -1,1950 +0,0 @@ -package server - -import ( - "context" - "errors" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -func TestAuthorizedToken(t *testing.T) { - var tests = []struct { - Desc string - Code int - Principal oauth2.Principal - ValidateErr error - Expected string - }{ - { - Desc: "Error in validate", - Code: http.StatusForbidden, - ValidateErr: errors.New("error"), - }, - { - Desc: "Authorized ok", - Code: http.StatusOK, - Principal: oauth2.Principal{ - Subject: "Principal Strickland", - }, - Expected: "Principal Strickland", - }, - } - for _, test := range tests { - // next is a sentinel StatusOK and - // principal recorder. - var principal oauth2.Principal - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - principal = r.Context().Value(oauth2.PrincipalKey).(oauth2.Principal) - }) - req, _ := http.NewRequest("GET", "", nil) - w := httptest.NewRecorder() - - a := &mocks.Authenticator{ - Principal: test.Principal, - ValidateErr: test.ValidateErr, - } - - logger := &chronograf.NoopLogger{} - handler := AuthorizedToken(a, logger, next) - handler.ServeHTTP(w, req) - if w.Code != test.Code { - t.Errorf("Status code expected: %d actual %d", test.Code, w.Code) - } else if principal != test.Principal { - t.Errorf("Principal mismatch expected: %s actual %s", test.Principal, principal) - } - } -} -func TestAuthorizedUser(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - OrganizationsStore chronograf.OrganizationsStore - Logger chronograf.Logger - } - type args struct { - principal *oauth2.Principal - scheme string - useAuth bool - role string - } - tests := []struct { - name string - fields fields - args args - hasOrganizationContext bool - hasSuperAdminContext bool - hasRoleContext bool - hasServerContext bool - authorized bool - }{ - { - name: "Not using auth", - fields: fields{ - UsersStore: &mocks.UsersStore{}, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - useAuth: false, - }, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: false, - hasServerContext: true, - authorized: true, - }, - { - name: "User with member role is member authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.MemberRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "member", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with viewer role is member authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.ViewerRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "member", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with editor role is member authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "member", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with admin role is member authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "member", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with viewer role is viewer authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.ViewerRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "viewer", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with editor role is viewer authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "viewer", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with admin role is viewer authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "viewer", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with viewer role is editor unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.ViewerRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "editor", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with editor role is editor authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "editor", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with admin role is editor authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "editor", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with viewer role is admin unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.ViewerRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with editor role is admin unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with admin role is admin authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: false, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "User with no role is viewer unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "view", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with no role is editor unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "editor", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with no role is admin unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with unknown role is viewer unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "sweet_role", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "viewer", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with unknown role is editor unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "sweet_role", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "editor", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with unknown role is admin unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "sweet_role", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with viewer role is SuperAdmin unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.ViewerRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "superadmin", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with editor role is SuperAdmin unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "superadmin", - useAuth: true, - }, - authorized: false, - }, - { - name: "User with admin role is SuperAdmin unauthorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "superadmin", - useAuth: true, - }, - authorized: false, - }, - { - name: "SuperAdmin is Viewer authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.MemberRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "viewer", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: true, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "SuperAdmin is Editor authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.MemberRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "editor", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: true, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "SuperAdmin is Admin authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.MemberRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: true, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "SuperAdmin is SuperAdmin authorized", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.MemberRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "superadmin", - useAuth: true, - }, - authorized: true, - hasOrganizationContext: true, - hasSuperAdminContext: true, - hasRoleContext: true, - hasServerContext: false, - }, - { - name: "Invalid principal – principal is nil", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: nil, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: false, - }, - { - name: "Invalid principal - missing organization", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: false, - }, - { - name: "Invalid principal - organization id not uint64", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1ee7", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: false, - }, - { - name: "Failed to retrieve organization", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - switch *q.ID { - case "1338": - return &chronograf.Organization{ - ID: "1338", - Name: "The ShillBillThrilliettas", - }, nil - default: - return nil, chronograf.ErrOrganizationNotFound - } - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billysteve", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: false, - }, - { - name: "Failed to retrieve user", - fields: fields{ - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - switch *q.Name { - case "billysteve": - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - default: - return nil, chronograf.ErrUserNotFound - } - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - principal: &oauth2.Principal{ - Subject: "billietta", - Issuer: "google", - Organization: "1337", - }, - scheme: "oauth2", - role: "admin", - useAuth: true, - }, - authorized: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var authorized bool - var hasServerCtx bool - var hasSuperAdminCtx bool - var hasOrganizationCtx bool - var hasRoleCtx bool - next := func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - hasServerCtx = hasServerContext(ctx) - hasSuperAdminCtx = hasSuperAdminContext(ctx) - _, hasOrganizationCtx = hasOrganizationContext(ctx) - _, hasRoleCtx = hasRoleContext(ctx) - authorized = true - } - fn := AuthorizedUser( - &Store{ - UsersStore: tt.fields.UsersStore, - OrganizationsStore: tt.fields.OrganizationsStore, - }, - tt.args.useAuth, - tt.args.role, - tt.fields.Logger, - next, - ) - - w := httptest.NewRecorder() - r := httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ) - if tt.args.principal == nil { - r = r.WithContext(context.WithValue(r.Context(), oauth2.PrincipalKey, nil)) - } else { - r = r.WithContext(context.WithValue(r.Context(), oauth2.PrincipalKey, *tt.args.principal)) - } - fn(w, r) - - if authorized != tt.authorized { - t.Errorf("%q. AuthorizedUser() = %v, expected %v", tt.name, authorized, tt.authorized) - } - - if !authorized && w.Code != http.StatusForbidden { - t.Errorf("%q. AuthorizedUser() Status Code = %v, expected %v", tt.name, w.Code, http.StatusForbidden) - } - - if hasServerCtx != tt.hasServerContext { - t.Errorf("%q. AuthorizedUser().Context().Server = %v, expected %v", tt.name, hasServerCtx, tt.hasServerContext) - } - - if hasSuperAdminCtx != tt.hasSuperAdminContext { - t.Errorf("%q. AuthorizedUser().Context().SuperAdmin = %v, expected %v", tt.name, hasSuperAdminCtx, tt.hasSuperAdminContext) - } - - if hasOrganizationCtx != tt.hasOrganizationContext { - t.Errorf("%q. AuthorizedUser.Context().Organization = %v, expected %v", tt.name, hasOrganizationCtx, tt.hasOrganizationContext) - } - - if hasRoleCtx != tt.hasRoleContext { - t.Errorf("%q. AuthorizedUser().Context().Role = %v, expected %v", tt.name, hasRoleCtx, tt.hasRoleContext) - } - - }) - } -} - -func TestRawStoreAccess(t *testing.T) { - type fields struct { - Logger chronograf.Logger - } - type args struct { - principal *oauth2.Principal - serverContext bool - user *chronograf.User - } - type wants struct { - authorized bool - hasServerContext bool - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "middleware already has server context", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - serverContext: true, - }, - wants: wants{ - authorized: true, - hasServerContext: true, - }, - }, - { - name: "user on context is a SuperAdmin", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - user: &chronograf.User{ - SuperAdmin: true, - }, - }, - wants: wants{ - authorized: true, - hasServerContext: true, - }, - }, - { - name: "user on context is a not SuperAdmin", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - }, - args: args{ - user: &chronograf.User{ - SuperAdmin: false, - }, - }, - wants: wants{ - authorized: false, - hasServerContext: false, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var authorized bool - var hasServerCtx bool - next := func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - hasServerCtx = hasServerContext(ctx) - authorized = true - } - fn := RawStoreAccess( - tt.fields.Logger, - next, - ) - - w := httptest.NewRecorder() - url := "http://any.url" - r := httptest.NewRequest( - "GET", - url, - nil, - ) - if tt.args.principal == nil { - r = r.WithContext(context.WithValue(r.Context(), oauth2.PrincipalKey, nil)) - } else { - r = r.WithContext(context.WithValue(r.Context(), oauth2.PrincipalKey, *tt.args.principal)) - } - - if tt.args.serverContext { - r = r.WithContext(serverContext(r.Context())) - } - if tt.args.user != nil { - r = r.WithContext(context.WithValue(r.Context(), UserContextKey, tt.args.user)) - } - fn(w, r) - - if authorized != tt.wants.authorized { - t.Errorf("%q. RawStoreAccess() = %v, expected %v", tt.name, authorized, tt.wants.authorized) - } - - if !authorized && w.Code != http.StatusForbidden { - t.Errorf("%q. RawStoreAccess() Status Code = %v, expected %v", tt.name, w.Code, http.StatusForbidden) - } - - if hasServerCtx != tt.wants.hasServerContext { - t.Errorf("%q. RawStoreAccess().Context().Server = %v, expected %v", tt.name, hasServerCtx, tt.wants.hasServerContext) - } - - }) - } -} diff --git a/chronograf/server/builders.go b/chronograf/server/builders.go deleted file mode 100644 index c3d9519cef4..00000000000 --- a/chronograf/server/builders.go +++ /dev/null @@ -1,186 +0,0 @@ -package server - -import ( - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/canned" - "github.com/influxdata/influxdb/v2/chronograf/filestore" - "github.com/influxdata/influxdb/v2/chronograf/memdb" - "github.com/influxdata/influxdb/v2/chronograf/multistore" -) - -// LayoutBuilder is responsible for building Layouts -type LayoutBuilder interface { - Build(chronograf.LayoutsStore) (*multistore.Layouts, error) -} - -// MultiLayoutBuilder implements LayoutBuilder and will return a Layouts -type MultiLayoutBuilder struct { - Logger chronograf.Logger - UUID chronograf.ID - CannedPath string -} - -// Build will construct a Layouts of canned and db-backed personalized -// layouts -func (builder *MultiLayoutBuilder) Build(db chronograf.LayoutsStore) (*multistore.Layouts, error) { - // These apps are those handled from a directory - apps := filestore.NewApps(builder.CannedPath, builder.UUID, builder.Logger) - // These apps are statically compiled into chronograf - binApps := &canned.BinLayoutsStore{ - Logger: builder.Logger, - } - // Acts as a front-end to both the bolt layouts, filesystem layouts and binary statically compiled layouts. - // The idea here is that these stores form a hierarchy in which each is tried sequentially until - // the operation has success. So, the database is preferred over filesystem over binary data. - layouts := &multistore.Layouts{ - Stores: []chronograf.LayoutsStore{ - db, - apps, - binApps, - }, - } - - return layouts, nil -} - -// DashboardBuilder is responsible for building dashboards -type DashboardBuilder interface { - Build(chronograf.DashboardsStore) (*multistore.DashboardsStore, error) -} - -// MultiDashboardBuilder builds a DashboardsStore backed by bolt and the filesystem -type MultiDashboardBuilder struct { - Logger chronograf.Logger - ID chronograf.ID - Path string -} - -// Build will construct a Dashboard store of filesystem and db-backed dashboards -func (builder *MultiDashboardBuilder) Build(db chronograf.DashboardsStore) (*multistore.DashboardsStore, error) { - // These dashboards are those handled from a directory - files := filestore.NewDashboards(builder.Path, builder.ID, builder.Logger) - // Acts as a front-end to both the bolt dashboard and filesystem dashboards. - // The idea here is that these stores form a hierarchy in which each is tried sequentially until - // the operation has success. So, the database is preferred over filesystem - dashboards := &multistore.DashboardsStore{ - Stores: []chronograf.DashboardsStore{ - db, - files, - }, - } - - return dashboards, nil -} - -// SourcesBuilder builds a MultiSourceStore -type SourcesBuilder interface { - Build(chronograf.SourcesStore) (*multistore.SourcesStore, error) -} - -// MultiSourceBuilder implements SourcesBuilder -type MultiSourceBuilder struct { - InfluxDBURL string - InfluxDBUsername string - InfluxDBPassword string - - Logger chronograf.Logger - ID chronograf.ID - Path string -} - -// Build will return a MultiSourceStore -func (fs *MultiSourceBuilder) Build(db chronograf.SourcesStore) (*multistore.SourcesStore, error) { - // These dashboards are those handled from a directory - files := filestore.NewSources(fs.Path, fs.ID, fs.Logger) - - stores := []chronograf.SourcesStore{db, files} - - if fs.InfluxDBURL != "" { - influxStore := &memdb.SourcesStore{ - Source: &chronograf.Source{ - ID: 0, - Name: fs.InfluxDBURL, - Type: chronograf.InfluxDB, - Username: fs.InfluxDBUsername, - Password: fs.InfluxDBPassword, - URL: fs.InfluxDBURL, - Default: true, - }} - stores = append([]chronograf.SourcesStore{influxStore}, stores...) - } - sources := &multistore.SourcesStore{ - Stores: stores, - } - - return sources, nil -} - -// KapacitorBuilder builds a KapacitorStore -type KapacitorBuilder interface { - Build(chronograf.ServersStore) (*multistore.KapacitorStore, error) -} - -// MultiKapacitorBuilder implements KapacitorBuilder -type MultiKapacitorBuilder struct { - KapacitorURL string - KapacitorUsername string - KapacitorPassword string - - Logger chronograf.Logger - ID chronograf.ID - Path string -} - -// Build will return a multistore facade KapacitorStore over memdb and bolt -func (builder *MultiKapacitorBuilder) Build(db chronograf.ServersStore) (*multistore.KapacitorStore, error) { - // These dashboards are those handled from a directory - files := filestore.NewKapacitors(builder.Path, builder.ID, builder.Logger) - - stores := []chronograf.ServersStore{db, files} - - if builder.KapacitorURL != "" { - memStore := &memdb.KapacitorStore{ - Kapacitor: &chronograf.Server{ - ID: 0, - SrcID: 0, - Name: builder.KapacitorURL, - URL: builder.KapacitorURL, - Username: builder.KapacitorUsername, - Password: builder.KapacitorPassword, - }, - } - stores = append([]chronograf.ServersStore{memStore}, stores...) - } - kapacitors := &multistore.KapacitorStore{ - Stores: stores, - } - return kapacitors, nil -} - -// OrganizationBuilder is responsible for building dashboards -type OrganizationBuilder interface { - Build(chronograf.OrganizationsStore) (*multistore.OrganizationsStore, error) -} - -// MultiOrganizationBuilder builds a OrganizationsStore backed by bolt and the filesystem -type MultiOrganizationBuilder struct { - Logger chronograf.Logger - Path string -} - -// Build will construct a Organization store of filesystem and db-backed dashboards -func (builder *MultiOrganizationBuilder) Build(db chronograf.OrganizationsStore) (*multistore.OrganizationsStore, error) { - // These organization are those handled from a directory - files := filestore.NewOrganizations(builder.Path, builder.Logger) - // Acts as a front-end to both the bolt org and filesystem orgs. - // The idea here is that these stores form a hierarchy in which each is tried sequentially until - // the operation has success. So, the database is preferred over filesystem - orgs := &multistore.OrganizationsStore{ - Stores: []chronograf.OrganizationsStore{ - db, - files, - }, - } - - return orgs, nil -} diff --git a/chronograf/server/builders_test.go b/chronograf/server/builders_test.go deleted file mode 100644 index ccd0b055778..00000000000 --- a/chronograf/server/builders_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package server_test - -import ( - "testing" - - "github.com/influxdata/influxdb/v2/chronograf/server" -) - -func TestLayoutBuilder(t *testing.T) { - var l server.LayoutBuilder = &server.MultiLayoutBuilder{} - layout, err := l.Build(nil) - if err != nil { - t.Fatalf("MultiLayoutBuilder can't build a MultiLayoutsStore: %v", err) - } - - if layout == nil { - t.Fatal("LayoutBuilder should have built a layout") - } -} - -func TestSourcesStoresBuilder(t *testing.T) { - var b server.SourcesBuilder = &server.MultiSourceBuilder{} - sources, err := b.Build(nil) - if err != nil { - t.Fatalf("MultiSourceBuilder can't build a MultiSourcesStore: %v", err) - } - if sources == nil { - t.Fatal("SourcesBuilder should have built a MultiSourceStore") - } -} diff --git a/chronograf/server/cells.go b/chronograf/server/cells.go deleted file mode 100644 index 1c1155afd1c..00000000000 --- a/chronograf/server/cells.go +++ /dev/null @@ -1,379 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - idgen "github.com/influxdata/influxdb/v2/chronograf/id" -) - -const ( - // DefaultWidth is used if not specified - DefaultWidth = 4 - // DefaultHeight is used if not specified - DefaultHeight = 4 -) - -type dashboardCellLinks struct { - Self string `json:"self"` // Self link mapping to this resource -} - -type dashboardCellResponse struct { - chronograf.DashboardCell - Links dashboardCellLinks `json:"links"` -} - -func newCellResponse(dID chronograf.DashboardID, cell chronograf.DashboardCell) dashboardCellResponse { - base := "/chronograf/v1/dashboards" - if cell.Queries == nil { - cell.Queries = []chronograf.DashboardQuery{} - } - if cell.CellColors == nil { - cell.CellColors = []chronograf.CellColor{} - } - - // Copy to handle race condition - newAxes := make(map[string]chronograf.Axis, len(cell.Axes)) - for k, v := range cell.Axes { - newAxes[k] = v - } - - // ensure x, y, and y2 axes always returned - for _, lbl := range []string{"x", "y", "y2"} { - if _, found := newAxes[lbl]; !found { - newAxes[lbl] = chronograf.Axis{ - Bounds: []string{"", ""}, - } - } - } - cell.Axes = newAxes - - return dashboardCellResponse{ - DashboardCell: cell, - Links: dashboardCellLinks{ - Self: fmt.Sprintf("%s/%d/cells/%s", base, dID, cell.ID), - }, - } -} - -func newCellResponses(dID chronograf.DashboardID, dcells []chronograf.DashboardCell) []dashboardCellResponse { - cells := make([]dashboardCellResponse, len(dcells)) - for i, cell := range dcells { - cells[i] = newCellResponse(dID, cell) - } - return cells -} - -// ValidDashboardCellRequest verifies that the dashboard cells have a query and -// have the correct axes specified -func ValidDashboardCellRequest(c *chronograf.DashboardCell) error { - if c == nil { - return fmt.Errorf("chronograf dashboard cell was nil") - } - - CorrectWidthHeight(c) - for _, q := range c.Queries { - if err := ValidateQueryConfig(&q.QueryConfig); err != nil { - return err - } - } - MoveTimeShift(c) - err := HasCorrectAxes(c) - if err != nil { - return err - } - if err = HasCorrectColors(c); err != nil { - return err - } - return HasCorrectLegend(c) -} - -// HasCorrectAxes verifies that only permitted axes exist within a DashboardCell -func HasCorrectAxes(c *chronograf.DashboardCell) error { - for label, axis := range c.Axes { - if !oneOf(label, "x", "y", "y2") { - return chronograf.ErrInvalidAxis - } - - if !oneOf(axis.Scale, "linear", "log", "") { - return chronograf.ErrInvalidAxis - } - - if !oneOf(axis.Base, "10", "2", "") { - return chronograf.ErrInvalidAxis - } - } - - return nil -} - -// HasCorrectColors verifies that the format of each color is correct -func HasCorrectColors(c *chronograf.DashboardCell) error { - for _, color := range c.CellColors { - if !oneOf(color.Type, "max", "min", "threshold", "text", "background", "scale") { - return chronograf.ErrInvalidColorType - } - if len(color.Hex) != 7 { - return chronograf.ErrInvalidColor - } - } - return nil -} - -// HasCorrectLegend verifies that the format of the legend is correct -func HasCorrectLegend(c *chronograf.DashboardCell) error { - // No legend set - if c.Legend.Type == "" && c.Legend.Orientation == "" { - return nil - } - - if c.Legend.Type == "" || c.Legend.Orientation == "" { - return chronograf.ErrInvalidLegend - } - if !oneOf(c.Legend.Orientation, "top", "bottom", "right", "left") { - return chronograf.ErrInvalidLegendOrient - } - - // Remember! if we add other types, update ErrInvalidLegendType - if !oneOf(c.Legend.Type, "static") { - return chronograf.ErrInvalidLegendType - } - return nil -} - -// oneOf reports whether a provided string is a member of a variadic list of -// valid options -func oneOf(prop string, validOpts ...string) bool { - for _, valid := range validOpts { - if prop == valid { - return true - } - } - return false -} - -// CorrectWidthHeight changes the cell to have at least the -// minimum width and height -func CorrectWidthHeight(c *chronograf.DashboardCell) { - if c.W < 1 { - c.W = DefaultWidth - } - if c.H < 1 { - c.H = DefaultHeight - } -} - -// MoveTimeShift moves TimeShift from the QueryConfig to the DashboardQuery -func MoveTimeShift(c *chronograf.DashboardCell) { - for i, query := range c.Queries { - query.Shifts = query.QueryConfig.Shifts - c.Queries[i] = query - } -} - -// AddQueryConfig updates a cell by converting InfluxQL into queryconfigs -// If influxql cannot be represented by a full query config, then, the -// query config's raw text is set to the command. -func AddQueryConfig(c *chronograf.DashboardCell) { - for i, q := range c.Queries { - qc := ToQueryConfig(q.Command) - qc.Shifts = append([]chronograf.TimeShift(nil), q.Shifts...) - q.Shifts = nil - q.QueryConfig = qc - c.Queries[i] = q - } -} - -// DashboardCells returns all cells from a dashboard within the store -func (s *Service) DashboardCells(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - e, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - boards := newDashboardResponse(e) - cells := boards.Cells - encodeJSON(w, http.StatusOK, cells, s.Logger) -} - -// NewDashboardCell adds a cell to an existing dashboard -func (s *Service) NewDashboardCell(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - dash, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - var cell chronograf.DashboardCell - if err := json.NewDecoder(r.Body).Decode(&cell); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := ValidDashboardCellRequest(&cell); err != nil { - invalidData(w, err, s.Logger) - return - } - - ids := &idgen.UUID{} - cid, err := ids.Generate() - if err != nil { - msg := fmt.Sprintf("Error creating cell ID of dashboard %d: %v", id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - cell.ID = cid - - dash.Cells = append(dash.Cells, cell) - if err := s.Store.Dashboards(ctx).Update(ctx, dash); err != nil { - msg := fmt.Sprintf("Error adding cell %s to dashboard %d: %v", cid, id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - boards := newDashboardResponse(dash) - for _, cell := range boards.Cells { - if cell.ID == cid { - encodeJSON(w, http.StatusOK, cell, s.Logger) - return - } - } -} - -// DashboardCellID gets a specific cell from an existing dashboard -func (s *Service) DashboardCellID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - dash, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - boards := newDashboardResponse(dash) - cid := httprouter.ParamsFromContext(ctx).ByName("cid") - for _, cell := range boards.Cells { - if cell.ID == cid { - encodeJSON(w, http.StatusOK, cell, s.Logger) - return - } - } - notFound(w, id, s.Logger) -} - -// RemoveDashboardCell removes a specific cell from an existing dashboard -func (s *Service) RemoveDashboardCell(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - dash, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - cid := httprouter.ParamsFromContext(ctx).ByName("cid") - cellid := -1 - for i, cell := range dash.Cells { - if cell.ID == cid { - cellid = i - break - } - } - if cellid == -1 { - notFound(w, id, s.Logger) - return - } - - dash.Cells = append(dash.Cells[:cellid], dash.Cells[cellid+1:]...) - if err := s.Store.Dashboards(ctx).Update(ctx, dash); err != nil { - msg := fmt.Sprintf("Error removing cell %s from dashboard %d: %v", cid, id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - w.WriteHeader(http.StatusNoContent) -} - -// ReplaceDashboardCell replaces a cell entirely within an existing dashboard -func (s *Service) ReplaceDashboardCell(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - dash, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - cid := httprouter.ParamsFromContext(ctx).ByName("cid") - cellid := -1 - for i, cell := range dash.Cells { - if cell.ID == cid { - cellid = i - break - } - } - if cellid == -1 { - notFound(w, cid, s.Logger) - return - } - - var cell chronograf.DashboardCell - if err := json.NewDecoder(r.Body).Decode(&cell); err != nil { - invalidJSON(w, s.Logger) - return - } - - for i, a := range cell.Axes { - if len(a.Bounds) == 0 { - a.Bounds = []string{"", ""} - cell.Axes[i] = a - } - } - - if err := ValidDashboardCellRequest(&cell); err != nil { - invalidData(w, err, s.Logger) - return - } - cell.ID = cid - - dash.Cells[cellid] = cell - if err := s.Store.Dashboards(ctx).Update(ctx, dash); err != nil { - msg := fmt.Sprintf("Error updating cell %s in dashboard %d: %v", cid, id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - res := newCellResponse(dash.ID, cell) - encodeJSON(w, http.StatusOK, res, s.Logger) -} diff --git a/chronograf/server/cells_test.go b/chronograf/server/cells_test.go deleted file mode 100644 index 811cc33e997..00000000000 --- a/chronograf/server/cells_test.go +++ /dev/null @@ -1,992 +0,0 @@ -package server - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "net/url" - "reflect" - "strings" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -func Test_Cells_CorrectAxis(t *testing.T) { - t.Parallel() - - axisTests := []struct { - name string - cell *chronograf.DashboardCell - shouldFail bool - }{ - { - name: "correct axes", - cell: &chronograf.DashboardCell{ - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Bounds: []string{"0", "100"}, - }, - "y": chronograf.Axis{ - Bounds: []string{"0", "100"}, - }, - "y2": chronograf.Axis{ - Bounds: []string{"0", "100"}, - }, - }, - }, - }, - { - name: "invalid axes present", - cell: &chronograf.DashboardCell{ - Axes: map[string]chronograf.Axis{ - "axis of evil": chronograf.Axis{ - Bounds: []string{"666", "666"}, - }, - "axis of awesome": chronograf.Axis{ - Bounds: []string{"1337", "31337"}, - }, - }, - }, - shouldFail: true, - }, - { - name: "linear scale value", - cell: &chronograf.DashboardCell{ - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Scale: "linear", - Bounds: []string{"0", "100"}, - }, - }, - }, - }, - { - name: "log scale value", - cell: &chronograf.DashboardCell{ - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Scale: "log", - Bounds: []string{"0", "100"}, - }, - }, - }, - }, - { - name: "invalid scale value", - cell: &chronograf.DashboardCell{ - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Scale: "potatoes", - Bounds: []string{"0", "100"}, - }, - }, - }, - shouldFail: true, - }, - { - name: "base 10 axis", - cell: &chronograf.DashboardCell{ - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Base: "10", - Bounds: []string{"0", "100"}, - }, - }, - }, - }, - { - name: "base 2 axis", - cell: &chronograf.DashboardCell{ - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Base: "2", - Bounds: []string{"0", "100"}, - }, - }, - }, - }, - { - name: "invalid base", - cell: &chronograf.DashboardCell{ - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Base: "all your base are belong to us", - Bounds: []string{"0", "100"}, - }, - }, - }, - shouldFail: true, - }, - } - - for _, test := range axisTests { - t.Run(test.name, func(tt *testing.T) { - if err := HasCorrectAxes(test.cell); err != nil && !test.shouldFail { - t.Errorf("%q: Unexpected error: err: %s", test.name, err) - } else if err == nil && test.shouldFail { - t.Errorf("%q: Expected error and received none", test.name) - } - }) - } -} - -func Test_Service_DashboardCells(t *testing.T) { - cellsTests := []struct { - name string - reqURL *url.URL - ctxParams map[string]string - mockResponse []chronograf.DashboardCell - expected []chronograf.DashboardCell - expectedCode int - }{ - { - name: "happy path", - reqURL: &url.URL{ - Path: "/chronograf/v1/dashboards/1/cells", - }, - ctxParams: map[string]string{ - "id": "1", - }, - mockResponse: []chronograf.DashboardCell{}, - expected: []chronograf.DashboardCell{}, - expectedCode: http.StatusOK, - }, - { - name: "cell axes should always be \"x\", \"y\", and \"y2\"", - reqURL: &url.URL{ - Path: "/chronograf/v1/dashboards/1/cells", - }, - ctxParams: map[string]string{ - "id": "1", - }, - mockResponse: []chronograf.DashboardCell{ - { - ID: "3899be5a-f6eb-4347-b949-de2f4fbea859", - X: 0, - Y: 0, - W: 4, - H: 4, - Name: "CPU", - Type: "bar", - Queries: []chronograf.DashboardQuery{}, - Axes: map[string]chronograf.Axis{}, - }, - }, - expected: []chronograf.DashboardCell{ - { - ID: "3899be5a-f6eb-4347-b949-de2f4fbea859", - X: 0, - Y: 0, - W: 4, - H: 4, - Name: "CPU", - Type: "bar", - Queries: []chronograf.DashboardQuery{}, - CellColors: []chronograf.CellColor{}, - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - "y": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - "y2": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - }, - }, - }, - expectedCode: http.StatusOK, - }, - } - - for _, test := range cellsTests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - // setup context with params - params := httprouter.Params{} - for k, v := range test.ctxParams { - params = append(params, httprouter.Param{ - Key: k, - Value: v, - }) - } - ctx := context.WithValue( - context.Background(), - httprouter.ParamsKey, - params, - ) - - // setup response recorder and request - rr := httptest.NewRecorder() - req := httptest.NewRequest("GET", test.reqURL.RequestURI(), strings.NewReader("")).WithContext(ctx) - - // setup mock DashboardCells store and logger - tlog := &mocks.TestLogger{} - svc := &Service{ - Store: &mocks.Store{ - DashboardsStore: &mocks.DashboardsStore{ - GetF: func(ctx context.Context, id chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{ - ID: chronograf.DashboardID(1), - Cells: test.mockResponse, - Templates: []chronograf.Template{}, - Name: "empty dashboard", - }, nil - }, - }, - }, - Logger: tlog, - } - - // invoke DashboardCell handler - svc.DashboardCells(rr, req) - - // setup frame to decode response into - respFrame := []struct { - chronograf.DashboardCell - Links json.RawMessage `json:"links"` // ignore links - }{} - - // decode response - resp := rr.Result() - - if resp.StatusCode != test.expectedCode { - tlog.Dump(t) - t.Fatalf("%q - Status codes do not match. Want %d (%s), Got %d (%s)", test.name, test.expectedCode, http.StatusText(test.expectedCode), resp.StatusCode, http.StatusText(resp.StatusCode)) - } - - if err := json.NewDecoder(resp.Body).Decode(&respFrame); err != nil { - t.Fatalf("%q - Error unmarshalling response body: err: %s", test.name, err) - } - - // extract actual - actual := []chronograf.DashboardCell{} - for _, rsp := range respFrame { - actual = append(actual, rsp.DashboardCell) - } - - // compare actual and expected - if !cmp.Equal(actual, test.expected) { - t.Fatalf("%q - Dashboard Cells do not match: diff: %s", test.name, cmp.Diff(actual, test.expected)) - } - }) - } -} - -func TestHasCorrectColors(t *testing.T) { - tests := []struct { - name string - c *chronograf.DashboardCell - wantErr bool - }{ - { - name: "min type is valid", - c: &chronograf.DashboardCell{ - CellColors: []chronograf.CellColor{ - { - Type: "min", - Hex: "#FFFFFF", - }, - }, - }, - }, - { - name: "max type is valid", - c: &chronograf.DashboardCell{ - CellColors: []chronograf.CellColor{ - { - Type: "max", - Hex: "#FFFFFF", - }, - }, - }, - }, - { - name: "threshold type is valid", - c: &chronograf.DashboardCell{ - CellColors: []chronograf.CellColor{ - { - Type: "threshold", - Hex: "#FFFFFF", - }, - }, - }, - }, - { - name: "invalid color type", - c: &chronograf.DashboardCell{ - CellColors: []chronograf.CellColor{ - { - Type: "unknown", - Hex: "#FFFFFF", - }, - }, - }, - wantErr: true, - }, - { - name: "invalid color hex", - c: &chronograf.DashboardCell{ - CellColors: []chronograf.CellColor{ - { - Type: "min", - Hex: "bad", - }, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := HasCorrectColors(tt.c); (err != nil) != tt.wantErr { - t.Errorf("HasCorrectColors() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestService_ReplaceDashboardCell(t *testing.T) { - tests := []struct { - name string - DashboardsStore chronograf.DashboardsStore - ID string - CID string - w *httptest.ResponseRecorder - r *http.Request - want string - }{ - { - name: "update cell retains query config", - ID: "1", - CID: "3c5c4102-fa40-4585-a8f9-917c77e37192", - DashboardsStore: &mocks.DashboardsStore{ - UpdateF: func(ctx context.Context, target chronograf.Dashboard) error { - return nil - }, - GetF: func(ctx context.Context, ID chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{ - ID: ID, - Cells: []chronograf.DashboardCell{ - { - ID: "3c5c4102-fa40-4585-a8f9-917c77e37192", - W: 4, - H: 4, - Name: "Untitled Cell", - Queries: []chronograf.DashboardQuery{ - { - Command: "SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time > :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)", - QueryConfig: chronograf.QueryConfig{ - ID: "3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e", - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Alias: "mean_usage_user", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{ - "cpu": { - "ChristohersMBP2.lan", - }, - }, - GroupBy: chronograf.GroupBy{ - Time: "2s", - Tags: []string{}, - }, - AreTagsAccepted: true, - Fill: "null", - RawText: strPtr("SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time > :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)"), - Range: &chronograf.DurationRange{ - Lower: "now() - 15m"}, - Shifts: []chronograf.TimeShift{}, - }, - }, - }, - Axes: map[string]chronograf.Axis{ - "x": { - Bounds: []string{"", ""}, - }, - "y": { - Bounds: []string{"", ""}, - }, - "y2": { - Bounds: []string{"", ""}, - }, - }, - Type: "line", - CellColors: []chronograf.CellColor{ - { - ID: "0", - Type: "min", - Hex: "#00C9FF", - Name: "laser", - Value: "0", - }, - { - ID: "1", - Type: "max", - Hex: "#9394FF", - Name: "comet", - Value: "100", - }, - }, - }, - }, - }, nil - }, - }, - w: httptest.NewRecorder(), - r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte(` - { - "i": "3c5c4102-fa40-4585-a8f9-917c77e37192", - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "name": "Untitled Cell", - "queries": [ - { - "queryConfig": { - "id": "3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e", - "database": "telegraf", - "measurement": "cpu", - "retentionPolicy": "autogen", - "fields": [ - { - "value": "mean", - "type": "func", - "alias": "mean_usage_user", - "args": [{"value": "usage_user", "type": "field", "alias": ""}] - } - ], - "tags": {"cpu": ["ChristohersMBP2.lan"]}, - "groupBy": {"time": "2s", "tags": []}, - "areTagsAccepted": true, - "fill": "null", - "rawText": - "SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time > :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)", - "range": {"upper": "", "lower": "now() - 15m"}, - "shifts": [] - }, - "query": - "SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time > :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)", - "source": null - } - ], - "axes": { - "x": { - "bounds": ["",""], - "label": "", - "prefix": "", - "suffix": "", - "base": "", - "scale": "" - }, - "y": { - "bounds": ["",""], - "label": "", - "prefix": "", - "suffix": "", - "base": "", - "scale": "" - }, - "y2": { - "bounds": ["",""], - "label": "", - "prefix": "", - "suffix": "", - "base": "", - "scale": "" - } - }, - "type": "line", - "colors": [ - {"type": "min", "hex": "#00C9FF", "id": "0", "name": "laser", "value": "0"}, - { - "type": "max", - "hex": "#9394FF", - "id": "1", - "name": "comet", - "value": "100" - } - ], - "links": { - "self": - "/chronograf/v1/dashboards/6/cells/3c5c4102-fa40-4585-a8f9-917c77e37192" - } - } - `))), - want: `{"i":"3c5c4102-fa40-4585-a8f9-917c77e37192","x":0,"y":0,"w":4,"h":4,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","queryConfig":{"id":"3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e","database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_user","args":[{"value":"usage_user","type":"field","alias":""}]}],"tags":{"cpu":["ChristohersMBP2.lan"]},"groupBy":{"time":"2s","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","range":{"upper":"","lower":"now() - 15m"},"shifts":[]},"source":""}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""}},"type":"line","colors":[{"id":"0","type":"min","hex":"#00C9FF","name":"laser","value":"0"},{"id":"1","type":"max","hex":"#9394FF","name":"comet","value":"100"}],"legend":{},"tableOptions":{"verticalTimeAxis":false,"sortBy":{"internalName":"","displayName":"","visible":false},"wrapping":"","fixFirstColumn":false},"fieldOptions":null,"timeFormat":"","decimalPlaces":{"isEnforced":false,"digits":0},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}} -`, - }, - { - name: "dashboard doesn't exist", - ID: "1", - DashboardsStore: &mocks.DashboardsStore{ - GetF: func(ctx context.Context, ID chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{}, fmt.Errorf("doesn't exist") - }, - }, - w: httptest.NewRecorder(), - r: httptest.NewRequest("PUT", "/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192", nil), - want: `{"code":404,"message":"ID 1 not found"}`, - }, - { - name: "cell doesn't exist", - ID: "1", - CID: "3c5c4102-fa40-4585-a8f9-917c77e37192", - DashboardsStore: &mocks.DashboardsStore{ - GetF: func(ctx context.Context, ID chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{}, nil - }, - }, - w: httptest.NewRecorder(), - r: httptest.NewRequest("PUT", "/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192", nil), - want: `{"code":404,"message":"ID 3c5c4102-fa40-4585-a8f9-917c77e37192 not found"}`, - }, - { - name: "invalid query config", - ID: "1", - CID: "3c5c4102-fa40-4585-a8f9-917c77e37192", - DashboardsStore: &mocks.DashboardsStore{ - GetF: func(ctx context.Context, ID chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{ - ID: ID, - Cells: []chronograf.DashboardCell{ - { - ID: "3c5c4102-fa40-4585-a8f9-917c77e37192", - }, - }, - }, nil - }, - }, - w: httptest.NewRecorder(), - r: httptest.NewRequest("PUT", "/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192", bytes.NewReader([]byte(`{ - "i": "3c5c4102-fa40-4585-a8f9-917c77e37192", - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "name": "Untitled Cell", - "queries": [ - { - "queryConfig": { - "fields": [ - { - "value": "invalid", - "type": "invalidType" - } - ] - } - } - ] - }`))), - want: `{"code":422,"message":"invalid field type \"invalidType\" ; expect func, field, integer, number, regex, wildcard"}`, - }, - { - name: "JSON is not parsable", - ID: "1", - CID: "3c5c4102-fa40-4585-a8f9-917c77e37192", - DashboardsStore: &mocks.DashboardsStore{ - GetF: func(ctx context.Context, ID chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{ - ID: ID, - Cells: []chronograf.DashboardCell{ - { - ID: "3c5c4102-fa40-4585-a8f9-917c77e37192", - }, - }, - }, nil - }, - }, - w: httptest.NewRecorder(), - r: httptest.NewRequest("PUT", "/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192", nil), - want: `{"code":400,"message":"unparsable JSON"}`, - }, - { - name: "not able to update store returns error message", - ID: "1", - CID: "3c5c4102-fa40-4585-a8f9-917c77e37192", - DashboardsStore: &mocks.DashboardsStore{ - UpdateF: func(ctx context.Context, target chronograf.Dashboard) error { - return fmt.Errorf("error") - }, - GetF: func(ctx context.Context, ID chronograf.DashboardID) (chronograf.Dashboard, error) { - return chronograf.Dashboard{ - ID: ID, - Cells: []chronograf.DashboardCell{ - { - ID: "3c5c4102-fa40-4585-a8f9-917c77e37192", - }, - }, - }, nil - }, - }, - w: httptest.NewRecorder(), - r: httptest.NewRequest("PUT", "/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192", bytes.NewReader([]byte(`{ - "i": "3c5c4102-fa40-4585-a8f9-917c77e37192", - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "name": "Untitled Cell", - "queries": [ - { - "queryConfig": { - "fields": [ - { - "value": "usage_user", - "type": "field" - } - ] - } - } - ] - }`))), - want: `{"code":500,"message":"Error updating cell 3c5c4102-fa40-4585-a8f9-917c77e37192 in dashboard 1: error"}`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - DashboardsStore: tt.DashboardsStore, - }, - Logger: &mocks.TestLogger{}, - } - tt.r = WithContext(tt.r.Context(), tt.r, map[string]string{ - "id": tt.ID, - "cid": tt.CID, - }) - tt.r = tt.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - { - Key: "cid", - Value: tt.CID, - }, - })) - s.ReplaceDashboardCell(tt.w, tt.r) - got := tt.w.Body.String() - if got != tt.want { - t.Errorf("ReplaceDashboardCell() = got/want\n%s\n%s\n", got, tt.want) - } - }) - } -} - -func strPtr(s string) *string { - return &s -} - -func Test_newCellResponses(t *testing.T) { - tests := []struct { - name string - dID chronograf.DashboardID - dcells []chronograf.DashboardCell - want []dashboardCellResponse - }{ - { - name: "all fields set", - dID: chronograf.DashboardID(1), - dcells: []chronograf.DashboardCell{ - chronograf.DashboardCell{ - ID: "445f8dc0-4d73-4168-8477-f628690d18a3", - X: 0, - Y: 0, - W: 4, - H: 4, - Name: "Untitled Cell", - Queries: []chronograf.DashboardQuery{ - { - Command: "SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time > :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)", - Label: "", - QueryConfig: chronograf.QueryConfig{ - ID: "8d5ec6da-13a5-423e-9026-7bc45649766c", - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Alias: "mean_usage_user", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - Alias: "", - }, - }, - }, - }, - Tags: map[string][]string{"cpu": []string{"ChristohersMBP2.lan"}}, - GroupBy: chronograf.GroupBy{ - Time: "2s", - }, - AreTagsAccepted: true, - Fill: "null", - RawText: strPtr("SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time > :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)"), - Range: &chronograf.DurationRange{ - Lower: "now() - 15m", - }, - }, - Source: "", - }, - }, - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{}, - "y": chronograf.Axis{}, - "y2": chronograf.Axis{}, - }, - Type: "line", - CellColors: []chronograf.CellColor{ - chronograf.CellColor{ID: "0", Type: "min", Hex: "#00C9FF", Name: "laser", Value: "0"}, - chronograf.CellColor{ID: "1", Type: "max", Hex: "#9394FF", Name: "comet", Value: "100"}, - }, - Legend: chronograf.Legend{ - Type: "static", - Orientation: "bottom", - }, - }, - }, - want: []dashboardCellResponse{ - { - DashboardCell: chronograf.DashboardCell{ - ID: "445f8dc0-4d73-4168-8477-f628690d18a3", - W: 4, - H: 4, - Name: "Untitled Cell", - Queries: []chronograf.DashboardQuery{ - { - Command: "SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time > :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)", - QueryConfig: chronograf.QueryConfig{ - ID: "8d5ec6da-13a5-423e-9026-7bc45649766c", - Database: "telegraf", - Measurement: "cpu", - RetentionPolicy: "autogen", - Fields: []chronograf.Field{ - { - Value: "mean", - Type: "func", - Alias: "mean_usage_user", - Args: []chronograf.Field{ - { - Value: "usage_user", - Type: "field", - }, - }, - }, - }, - Tags: map[string][]string{"cpu": {"ChristohersMBP2.lan"}}, - GroupBy: chronograf.GroupBy{ - Time: "2s", - }, - AreTagsAccepted: true, - Fill: "null", - RawText: strPtr("SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time > :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)"), - Range: &chronograf.DurationRange{ - Lower: "now() - 15m", - }, - }, - }, - }, - Axes: map[string]chronograf.Axis{ - "x": {}, - "y": {}, - "y2": {}, - }, - Type: "line", - CellColors: []chronograf.CellColor{ - { - ID: "0", - Type: "min", - Hex: "#00C9FF", - Name: "laser", - Value: "0", - }, - { - ID: "1", - Type: "max", - Hex: "#9394FF", - Name: "comet", - Value: "100", - }, - }, - Legend: chronograf.Legend{ - Type: "static", - Orientation: "bottom", - }, - }, - Links: dashboardCellLinks{ - Self: "/chronograf/v1/dashboards/1/cells/445f8dc0-4d73-4168-8477-f628690d18a3"}, - }, - }, - }, - { - name: "nothing set", - dID: chronograf.DashboardID(1), - dcells: []chronograf.DashboardCell{ - chronograf.DashboardCell{ - ID: "445f8dc0-4d73-4168-8477-f628690d18a3", - X: 0, - Y: 0, - W: 4, - H: 4, - Name: "Untitled Cell", - }, - }, - want: []dashboardCellResponse{ - { - DashboardCell: chronograf.DashboardCell{ - ID: "445f8dc0-4d73-4168-8477-f628690d18a3", - W: 4, - H: 4, - Name: "Untitled Cell", - Queries: []chronograf.DashboardQuery{}, - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - "y": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - "y2": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - }, - CellColors: []chronograf.CellColor{}, - Legend: chronograf.Legend{}, - }, - Links: dashboardCellLinks{ - Self: "/chronograf/v1/dashboards/1/cells/445f8dc0-4d73-4168-8477-f628690d18a3"}, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := newCellResponses(tt.dID, tt.dcells); !reflect.DeepEqual(got, tt.want) { - t.Errorf("newCellResponses() = got-/want+ %s", cmp.Diff(got, tt.want)) - } - }) - } -} - -func TestHasCorrectLegend(t *testing.T) { - tests := []struct { - name string - c *chronograf.DashboardCell - wantErr bool - }{ - { - name: "empty legend is ok", - c: &chronograf.DashboardCell{}, - }, - { - name: "must have both an orientation and type", - c: &chronograf.DashboardCell{ - Legend: chronograf.Legend{ - Type: "static", - }, - }, - wantErr: true, - }, - { - name: "must have both a type and orientation", - c: &chronograf.DashboardCell{ - Legend: chronograf.Legend{ - Orientation: "bottom", - }, - }, - wantErr: true, - }, - { - name: "invalid types", - c: &chronograf.DashboardCell{ - Legend: chronograf.Legend{ - Type: "no such type", - Orientation: "bottom", - }, - }, - wantErr: true, - }, - { - name: "invalid orientation", - c: &chronograf.DashboardCell{ - Legend: chronograf.Legend{ - Type: "static", - Orientation: "no such orientation", - }, - }, - wantErr: true, - }, - { - name: "orientation bottom valid", - c: &chronograf.DashboardCell{ - Legend: chronograf.Legend{ - Type: "static", - Orientation: "bottom", - }, - }, - }, - { - name: "orientation top valid", - c: &chronograf.DashboardCell{ - Legend: chronograf.Legend{ - Type: "static", - Orientation: "top", - }, - }, - }, - { - name: "orientation right valid", - c: &chronograf.DashboardCell{ - Legend: chronograf.Legend{ - Type: "static", - Orientation: "right", - }, - }, - }, - { - name: "orientation left valid", - c: &chronograf.DashboardCell{ - Legend: chronograf.Legend{ - Type: "static", - Orientation: "left", - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := HasCorrectLegend(tt.c); (err != nil) != tt.wantErr { - t.Errorf("HasCorrectLegend() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/chronograf/server/config.go b/chronograf/server/config.go deleted file mode 100644 index cb5d07802cd..00000000000 --- a/chronograf/server/config.go +++ /dev/null @@ -1,115 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -type configLinks struct { - Self string `json:"self"` // Self link mapping to this resource - Auth string `json:"auth"` // Auth link to the auth config endpoint -} - -type selfLinks struct { - Self string `json:"self"` // Self link mapping to this resource -} - -type configResponse struct { - Links configLinks `json:"links"` - chronograf.Config -} - -func newConfigResponse(config chronograf.Config) *configResponse { - return &configResponse{ - Links: configLinks{ - Self: "/chronograf/v1/config", - Auth: "/chronograf/v1/config/auth", - }, - Config: config, - } -} - -type authConfigResponse struct { - Links selfLinks `json:"links"` - chronograf.AuthConfig -} - -func newAuthConfigResponse(config chronograf.Config) *authConfigResponse { - return &authConfigResponse{ - Links: selfLinks{ - Self: "/chronograf/v1/config/auth", - }, - AuthConfig: config.Auth, - } -} - -// Config retrieves the global application configuration -func (s *Service) Config(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - config, err := s.Store.Config(ctx).Get(ctx) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - if config == nil { - Error(w, http.StatusBadRequest, "Configuration object was nil", s.Logger) - return - } - res := newConfigResponse(*config) - - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// AuthConfig retrieves the auth section of the global application configuration -func (s *Service) AuthConfig(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - config, err := s.Store.Config(ctx).Get(ctx) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - if config == nil { - Error(w, http.StatusBadRequest, "Configuration object was nil", s.Logger) - return - } - - res := newAuthConfigResponse(*config) - - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// ReplaceAuthConfig replaces the auth section of the global application configuration -func (s *Service) ReplaceAuthConfig(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - var authConfig chronograf.AuthConfig - if err := json.NewDecoder(r.Body).Decode(&authConfig); err != nil { - invalidJSON(w, s.Logger) - return - } - - config, err := s.Store.Config(ctx).Get(ctx) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - if config == nil { - Error(w, http.StatusBadRequest, "Configuration object was nil", s.Logger) - return - } - config.Auth = authConfig - - res := newAuthConfigResponse(*config) - if err := s.Store.Config(ctx).Update(ctx, config); err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - encodeJSON(w, http.StatusOK, res, s.Logger) -} diff --git a/chronograf/server/config_test.go b/chronograf/server/config_test.go deleted file mode 100644 index d9633ebcaef..00000000000 --- a/chronograf/server/config_test.go +++ /dev/null @@ -1,218 +0,0 @@ -package server - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -func TestConfig(t *testing.T) { - type fields struct { - ConfigStore chronograf.ConfigStore - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - wants wants - }{ - { - name: "Get global application configuration", - fields: fields{ - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - }, - wants: wants{ - statusCode: 200, - contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":{"superAdminNewUsers":false}}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - ConfigStore: tt.fields.ConfigStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - - s.Config(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. Config() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. Config() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. Config() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} - -func TestAuthConfig(t *testing.T) { - type fields struct { - ConfigStore chronograf.ConfigStore - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - wants wants - }{ - { - name: "Get auth configuration", - fields: fields{ - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - }, - wants: wants{ - statusCode: 200, - contentType: "application/json", - body: `{"superAdminNewUsers": false, "links": {"self": "/chronograf/v1/config/auth"}}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - ConfigStore: tt.fields.ConfigStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - - s.AuthConfig(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. Config() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. Config() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. Config() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} - -func TestReplaceAuthConfig(t *testing.T) { - type fields struct { - ConfigStore chronograf.ConfigStore - } - type args struct { - payload interface{} // expects JSON serializable struct - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "Set auth configuration", - fields: fields{ - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - }, - args: args{ - payload: chronograf.AuthConfig{ - SuperAdminNewUsers: true, - }, - }, - wants: wants{ - statusCode: 200, - contentType: "application/json", - body: `{"superAdminNewUsers": true, "links": {"self": "/chronograf/v1/config/auth"}}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - ConfigStore: tt.fields.ConfigStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - buf, _ := json.Marshal(tt.args.payload) - r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - - s.ReplaceAuthConfig(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. Config() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. Config() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. Config() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} diff --git a/chronograf/server/context.go b/chronograf/server/context.go deleted file mode 100644 index 5cd4ea37972..00000000000 --- a/chronograf/server/context.go +++ /dev/null @@ -1,30 +0,0 @@ -package server - -import ( - "context" -) - -type serverContextKey string - -// ServerContextKey is the key used to specify that the -// server is making the requet via context -const ServerContextKey = serverContextKey("server") - -// hasServerContext specifies if the context contains -// the ServerContextKey and that the value stored there is true -func hasServerContext(ctx context.Context) bool { - // prevents panic in case of nil context - if ctx == nil { - return false - } - sa, ok := ctx.Value(ServerContextKey).(bool) - // should never happen - if !ok { - return false - } - return sa -} - -func serverContext(ctx context.Context) context.Context { - return context.WithValue(ctx, ServerContextKey, true) -} diff --git a/chronograf/server/dashboards.go b/chronograf/server/dashboards.go deleted file mode 100644 index 0e74c5b5334..00000000000 --- a/chronograf/server/dashboards.go +++ /dev/null @@ -1,287 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -type dashboardLinks struct { - Self string `json:"self"` // Self link mapping to this resource - Cells string `json:"cells"` // Cells link to the cells endpoint - Templates string `json:"templates"` // Templates link to the templates endpoint -} - -type dashboardResponse struct { - ID chronograf.DashboardID `json:"id"` - Cells []dashboardCellResponse `json:"cells"` - Templates []templateResponse `json:"templates"` - Name string `json:"name"` - Organization string `json:"organization"` - Links dashboardLinks `json:"links"` -} - -type getDashboardsResponse struct { - Dashboards []*dashboardResponse `json:"dashboards"` -} - -func newDashboardResponse(d chronograf.Dashboard) *dashboardResponse { - base := "/chronograf/v1/dashboards" - dd := AddQueryConfigs(DashboardDefaults(d)) - cells := newCellResponses(dd.ID, dd.Cells) - templates := newTemplateResponses(dd.ID, dd.Templates) - - return &dashboardResponse{ - ID: dd.ID, - Name: dd.Name, - Cells: cells, - Templates: templates, - Organization: d.Organization, - Links: dashboardLinks{ - Self: fmt.Sprintf("%s/%d", base, dd.ID), - Cells: fmt.Sprintf("%s/%d/cells", base, dd.ID), - Templates: fmt.Sprintf("%s/%d/templates", base, dd.ID), - }, - } -} - -// Dashboards returns all dashboards within the store -func (s *Service) Dashboards(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - dashboards, err := s.Store.Dashboards(ctx).All(ctx) - if err != nil { - Error(w, http.StatusInternalServerError, "Error loading dashboards", s.Logger) - return - } - - res := getDashboardsResponse{ - Dashboards: []*dashboardResponse{}, - } - - for _, dashboard := range dashboards { - res.Dashboards = append(res.Dashboards, newDashboardResponse(dashboard)) - } - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// DashboardID returns a single specified dashboard -func (s *Service) DashboardID(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - e, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - res := newDashboardResponse(e) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// NewDashboard creates and returns a new dashboard object -func (s *Service) NewDashboard(w http.ResponseWriter, r *http.Request) { - var dashboard chronograf.Dashboard - var err error - if err := json.NewDecoder(r.Body).Decode(&dashboard); err != nil { - invalidJSON(w, s.Logger) - return - } - - ctx := r.Context() - defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - if err := ValidDashboardRequest(&dashboard, defaultOrg.ID); err != nil { - invalidData(w, err, s.Logger) - return - } - - if dashboard, err = s.Store.Dashboards(ctx).Add(r.Context(), dashboard); err != nil { - msg := fmt.Errorf("error storing dashboard %v: %v", dashboard, err) - unknownErrorWithMessage(w, msg, s.Logger) - return - } - - res := newDashboardResponse(dashboard) - location(w, res.Links.Self) - encodeJSON(w, http.StatusCreated, res, s.Logger) -} - -// RemoveDashboard deletes a dashboard -func (s *Service) RemoveDashboard(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - e, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - if err := s.Store.Dashboards(ctx).Delete(ctx, e); err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - w.WriteHeader(http.StatusNoContent) -} - -// ReplaceDashboard completely replaces a dashboard -func (s *Service) ReplaceDashboard(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - idParam, err := paramID("id", r) - if err != nil { - msg := fmt.Sprintf("Could not parse dashboard ID: %s", err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - } - id := chronograf.DashboardID(idParam) - - _, err = s.Store.Dashboards(ctx).Get(ctx, id) - if err != nil { - Error(w, http.StatusNotFound, fmt.Sprintf("ID %d not found", id), s.Logger) - return - } - - var req chronograf.Dashboard - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - req.ID = id - - defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - if err := ValidDashboardRequest(&req, defaultOrg.ID); err != nil { - invalidData(w, err, s.Logger) - return - } - - if err := s.Store.Dashboards(ctx).Update(ctx, req); err != nil { - msg := fmt.Sprintf("Error updating dashboard ID %d: %v", id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - res := newDashboardResponse(req) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// UpdateDashboard completely updates either the dashboard name or the cells -func (s *Service) UpdateDashboard(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - idParam, err := paramID("id", r) - if err != nil { - msg := fmt.Sprintf("Could not parse dashboard ID: %s", err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - id := chronograf.DashboardID(idParam) - - orig, err := s.Store.Dashboards(ctx).Get(ctx, id) - if err != nil { - Error(w, http.StatusNotFound, fmt.Sprintf("ID %d not found", id), s.Logger) - return - } - - var req chronograf.Dashboard - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - req.ID = id - - if req.Name != "" { - orig.Name = req.Name - } else if len(req.Cells) > 0 { - defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - if err := ValidDashboardRequest(&req, defaultOrg.ID); err != nil { - invalidData(w, err, s.Logger) - return - } - orig.Cells = req.Cells - } else { - invalidData(w, fmt.Errorf("update must include either name or cells"), s.Logger) - return - } - - if err := s.Store.Dashboards(ctx).Update(ctx, orig); err != nil { - msg := fmt.Sprintf("Error updating dashboard ID %d: %v", id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - res := newDashboardResponse(orig) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// ValidDashboardRequest verifies that the dashboard cells have a query -func ValidDashboardRequest(d *chronograf.Dashboard, defaultOrgID string) error { - if d.Organization == "" { - d.Organization = defaultOrgID - } - for i, c := range d.Cells { - if err := ValidDashboardCellRequest(&c); err != nil { - return err - } - d.Cells[i] = c - } - for _, t := range d.Templates { - if err := ValidTemplateRequest(&t); err != nil { - return err - } - } - (*d) = DashboardDefaults(*d) - return nil -} - -// DashboardDefaults updates the dashboard with the default values -// if none are specified -func DashboardDefaults(d chronograf.Dashboard) (newDash chronograf.Dashboard) { - newDash.ID = d.ID - newDash.Templates = d.Templates - newDash.Name = d.Name - newDash.Organization = d.Organization - newDash.Cells = make([]chronograf.DashboardCell, len(d.Cells)) - - for i, c := range d.Cells { - CorrectWidthHeight(&c) - newDash.Cells[i] = c - } - return -} - -// AddQueryConfigs updates all the celsl in the dashboard to have query config -// objects corresponding to their influxql queries. -func AddQueryConfigs(d chronograf.Dashboard) (newDash chronograf.Dashboard) { - newDash.ID = d.ID - newDash.Templates = d.Templates - newDash.Name = d.Name - newDash.Cells = make([]chronograf.DashboardCell, len(d.Cells)) - - for i, c := range d.Cells { - AddQueryConfig(&c) - newDash.Cells[i] = c - } - return -} diff --git a/chronograf/server/dashboards_test.go b/chronograf/server/dashboards_test.go deleted file mode 100644 index a5ef0b7d9a9..00000000000 --- a/chronograf/server/dashboards_test.go +++ /dev/null @@ -1,366 +0,0 @@ -package server - -import ( - "reflect" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestCorrectWidthHeight(t *testing.T) { - t.Parallel() - tests := []struct { - name string - cell chronograf.DashboardCell - want chronograf.DashboardCell - }{ - { - name: "updates width", - cell: chronograf.DashboardCell{ - W: 0, - H: 4, - }, - want: chronograf.DashboardCell{ - W: 4, - H: 4, - }, - }, - { - name: "updates height", - cell: chronograf.DashboardCell{ - W: 4, - H: 0, - }, - want: chronograf.DashboardCell{ - W: 4, - H: 4, - }, - }, - { - name: "updates both", - cell: chronograf.DashboardCell{ - W: 0, - H: 0, - }, - want: chronograf.DashboardCell{ - W: 4, - H: 4, - }, - }, - { - name: "updates neither", - cell: chronograf.DashboardCell{ - W: 4, - H: 4, - }, - want: chronograf.DashboardCell{ - W: 4, - H: 4, - }, - }, - } - for _, tt := range tests { - if CorrectWidthHeight(&tt.cell); !reflect.DeepEqual(tt.cell, tt.want) { - t.Errorf("%q. CorrectWidthHeight() = %v, want %v", tt.name, tt.cell, tt.want) - } - } -} - -func TestDashboardDefaults(t *testing.T) { - tests := []struct { - name string - d chronograf.Dashboard - want chronograf.Dashboard - }{ - { - name: "Updates all cell widths/heights", - d: chronograf.Dashboard{ - Cells: []chronograf.DashboardCell{ - { - W: 0, - H: 0, - }, - { - W: 2, - H: 2, - }, - }, - }, - want: chronograf.Dashboard{ - Cells: []chronograf.DashboardCell{ - { - W: 4, - H: 4, - }, - { - W: 2, - H: 2, - }, - }, - }, - }, - { - name: "Updates no cell", - d: chronograf.Dashboard{ - Cells: []chronograf.DashboardCell{ - { - W: 4, - H: 4, - }, { - W: 2, - H: 2, - }, - }, - }, - want: chronograf.Dashboard{ - Cells: []chronograf.DashboardCell{ - { - W: 4, - H: 4, - }, - { - W: 2, - H: 2, - }, - }, - }, - }, - } - for _, tt := range tests { - if actual := DashboardDefaults(tt.d); !reflect.DeepEqual(actual, tt.want) { - t.Errorf("%q. DashboardDefaults() = %v, want %v", tt.name, tt.d, tt.want) - } - } -} - -func TestValidDashboardRequest(t *testing.T) { - tests := []struct { - name string - d chronograf.Dashboard - want chronograf.Dashboard - wantErr bool - }{ - { - name: "Updates all cell widths/heights", - d: chronograf.Dashboard{ - Organization: "1337", - Cells: []chronograf.DashboardCell{ - { - W: 0, - H: 0, - Queries: []chronograf.DashboardQuery{ - { - Command: "SELECT donors from hill_valley_preservation_society where time > 1985-10-25T08:00:00", - }, - }, - }, - { - W: 2, - H: 2, - Queries: []chronograf.DashboardQuery{ - { - Command: "SELECT winning_horses from grays_sports_alamanc where time > 1955-11-1T00:00:00", - }, - }, - }, - }, - }, - want: chronograf.Dashboard{ - Organization: "1337", - Cells: []chronograf.DashboardCell{ - { - W: 4, - H: 4, - Queries: []chronograf.DashboardQuery{ - { - Command: "SELECT donors from hill_valley_preservation_society where time > 1985-10-25T08:00:00", - }, - }, - }, - { - W: 2, - H: 2, - Queries: []chronograf.DashboardQuery{ - { - Command: "SELECT winning_horses from grays_sports_alamanc where time > 1955-11-1T00:00:00", - }, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - // TODO(desa): this Okay? - err := ValidDashboardRequest(&tt.d, "0") - if (err != nil) != tt.wantErr { - t.Errorf("%q. ValidDashboardRequest() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if diff := cmp.Diff(tt.d, tt.want); diff != "" { - t.Errorf("%q. ValidDashboardRequest(). got/want diff:\n%s", tt.name, diff) - } - } -} - -func Test_newDashboardResponse(t *testing.T) { - tests := []struct { - name string - d chronograf.Dashboard - want *dashboardResponse - }{ - { - name: "creates a dashboard response", - d: chronograf.Dashboard{ - Organization: "0", - Cells: []chronograf.DashboardCell{ - { - ID: "a", - W: 0, - H: 0, - Queries: []chronograf.DashboardQuery{ - { - Source: "/chronograf/v1/sources/1", - Command: "SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'", - Shifts: []chronograf.TimeShift{ - { - Label: "Best Week Evar", - Unit: "d", - Quantity: "7", - }, - }, - }, - }, - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Bounds: []string{"0", "100"}, - }, - "y": chronograf.Axis{ - Bounds: []string{"2", "95"}, - Label: "foo", - }, - }, - }, - { - ID: "b", - W: 0, - H: 0, - Queries: []chronograf.DashboardQuery{ - { - Source: "/chronograf/v1/sources/2", - Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m", - }, - }, - }, - }, - }, - want: &dashboardResponse{ - Organization: "0", - Templates: []templateResponse{}, - Cells: []dashboardCellResponse{ - dashboardCellResponse{ - Links: dashboardCellLinks{ - Self: "/chronograf/v1/dashboards/0/cells/a", - }, - DashboardCell: chronograf.DashboardCell{ - ID: "a", - W: 4, - H: 4, - Queries: []chronograf.DashboardQuery{ - { - Command: "SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'", - Source: "/chronograf/v1/sources/1", - QueryConfig: chronograf.QueryConfig{ - RawText: &[]string{"SELECT donors from hill_valley_preservation_society where time > '1985-10-25 08:00:00'"}[0], - Fields: []chronograf.Field{}, - GroupBy: chronograf.GroupBy{ - Tags: []string{}, - }, - Tags: make(map[string][]string), - AreTagsAccepted: false, - Shifts: []chronograf.TimeShift{ - { - Label: "Best Week Evar", - Unit: "d", - Quantity: "7", - }, - }, - }, - }, - }, - CellColors: []chronograf.CellColor{}, - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Bounds: []string{"0", "100"}, - }, - "y": chronograf.Axis{ - Bounds: []string{"2", "95"}, - Label: "foo", - }, - "y2": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - }, - }, - }, - dashboardCellResponse{ - Links: dashboardCellLinks{ - Self: "/chronograf/v1/dashboards/0/cells/b", - }, - DashboardCell: chronograf.DashboardCell{ - ID: "b", - W: 4, - H: 4, - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - "y": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - "y2": chronograf.Axis{ - Bounds: []string{"", ""}, - }, - }, - CellColors: []chronograf.CellColor{}, - Queries: []chronograf.DashboardQuery{ - { - Command: "SELECT winning_horses from grays_sports_alamanc where time > now() - 15m", - Source: "/chronograf/v1/sources/2", - QueryConfig: chronograf.QueryConfig{ - Measurement: "grays_sports_alamanc", - Fields: []chronograf.Field{ - { - Type: "field", - Value: "winning_horses", - }, - }, - GroupBy: chronograf.GroupBy{ - Tags: []string{}, - }, - Tags: make(map[string][]string), - AreTagsAccepted: false, - Range: &chronograf.DurationRange{ - Lower: "now() - 15m", - }, - }, - }, - }, - }, - }, - }, - Links: dashboardLinks{ - Self: "/chronograf/v1/dashboards/0", - Cells: "/chronograf/v1/dashboards/0/cells", - Templates: "/chronograf/v1/dashboards/0/templates", - }, - }, - }, - } - for _, tt := range tests { - if got := newDashboardResponse(tt.d); !cmp.Equal(got, tt.want) { - t.Errorf("%q. newDashboardResponse() = diff:\n%s", tt.name, cmp.Diff(got, tt.want)) - } - } -} diff --git a/chronograf/server/databases.go b/chronograf/server/databases.go deleted file mode 100644 index d79ea29d1ff..00000000000 --- a/chronograf/server/databases.go +++ /dev/null @@ -1,519 +0,0 @@ -package server - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strconv" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" -) - -const ( - limitQuery = "limit" - offsetQuery = "offset" -) - -type dbLinks struct { - Self string `json:"self"` // Self link mapping to this resource - RPs string `json:"retentionPolicies"` // URL for retention policies for this database - Measurements string `json:"measurements"` // URL for measurements for this database -} - -type dbResponse struct { - Name string `json:"name"` // a unique string identifier for the database - 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"` // RPs are the retention policies for a database - Links dbLinks `json:"links"` // Links are URI locations related to the database -} - -// newDBResponse creates the response for the /databases endpoint -func newDBResponse(srcID int, db string, rps []rpResponse) dbResponse { - base := "/chronograf/v1/sources" - return dbResponse{ - Name: db, - RPs: rps, - Links: dbLinks{ - Self: fmt.Sprintf("%s/%d/dbs/%s", base, srcID, db), - RPs: fmt.Sprintf("%s/%d/dbs/%s/rps", base, srcID, db), - Measurements: fmt.Sprintf("%s/%d/dbs/%s/measurements?limit=100&offset=0", base, srcID, db), - }, - } -} - -type dbsResponse struct { - Databases []dbResponse `json:"databases"` -} - -type rpLinks struct { - Self string `json:"self"` // Self link mapping to this resource -} - -type rpResponse struct { - Name string `json:"name"` // a unique string identifier for the retention policy - Duration string `json:"duration"` // the duration - Replication int32 `json:"replication"` // the replication factor - ShardDuration string `json:"shardDuration"` // the shard duration - Default bool `json:"isDefault"` // whether the RP should be the default - Links rpLinks `json:"links"` // Links are URI locations related to the database -} - -// WithLinks adds links to an rpResponse in place -func (r *rpResponse) WithLinks(srcID int, db string) { - base := "/chronograf/v1/sources" - r.Links = rpLinks{ - Self: fmt.Sprintf("%s/%d/dbs/%s/rps/%s", base, srcID, db, r.Name), - } -} - -type measurementLinks struct { - Self string `json:"self"` - First string `json:"first"` - Next string `json:"next,omitempty"` - Prev string `json:"prev,omitempty"` -} - -func newMeasurementLinks(src int, db string, limit, offset int) measurementLinks { - base := "/chronograf/v1/sources" - res := measurementLinks{ - Self: fmt.Sprintf("%s/%d/dbs/%s/measurements?limit=%d&offset=%d", base, src, db, limit, offset), - First: fmt.Sprintf("%s/%d/dbs/%s/measurements?limit=%d&offset=0", base, src, db, limit), - Next: fmt.Sprintf("%s/%d/dbs/%s/measurements?limit=%d&offset=%d", base, src, db, limit, offset+limit), - } - if offset-limit > 0 { - res.Prev = fmt.Sprintf("%s/%d/dbs/%s/measurements?limit=%d&offset=%d", base, src, db, limit, offset-limit) - } - - return res -} - -type measurementsResponse struct { - Measurements []chronograf.Measurement `json:"measurements"` // names of all measurements - Links measurementLinks `json:"links"` // Links are the URI locations for measurements pages -} - -// GetDatabases queries the list of all databases for a source -func (h *Service) GetDatabases(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) - return - } - - src, err := h.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, h.Logger) - return - } - - dbsvc := h.Databases - if err = dbsvc.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, h.Logger) - return - } - - databases, err := dbsvc.AllDB(ctx) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), h.Logger) - return - } - - dbs := make([]dbResponse, len(databases)) - for i, d := range databases { - rps, err := h.allRPs(ctx, dbsvc, srcID, d.Name) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), h.Logger) - return - } - dbs[i] = newDBResponse(srcID, d.Name, rps) - } - - res := dbsResponse{ - Databases: dbs, - } - - encodeJSON(w, http.StatusOK, res, h.Logger) -} - -// NewDatabase creates a new database within the datastore -func (h *Service) NewDatabase(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) - return - } - - src, err := h.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, h.Logger) - return - } - - dbsvc := h.Databases - - if err = dbsvc.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, h.Logger) - return - } - - postedDB := &chronograf.Database{} - if err := json.NewDecoder(r.Body).Decode(postedDB); err != nil { - invalidJSON(w, h.Logger) - return - } - - if err := ValidDatabaseRequest(postedDB); err != nil { - invalidData(w, err, h.Logger) - return - } - - database, err := dbsvc.CreateDB(ctx, postedDB) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), h.Logger) - return - } - - rps, err := h.allRPs(ctx, dbsvc, srcID, database.Name) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), h.Logger) - return - } - res := newDBResponse(srcID, database.Name, rps) - encodeJSON(w, http.StatusCreated, res, h.Logger) -} - -// DropDatabase removes a database from a data source -func (h *Service) DropDatabase(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) - return - } - - src, err := h.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, h.Logger) - return - } - - dbsvc := h.Databases - - if err = dbsvc.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, h.Logger) - return - } - - db := httprouter.ParamsFromContext(ctx).ByName("db") - - dropErr := dbsvc.DropDB(ctx, db) - if dropErr != nil { - Error(w, http.StatusBadRequest, dropErr.Error(), h.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -// RetentionPolicies lists retention policies within a database -func (h *Service) RetentionPolicies(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) - return - } - - src, err := h.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, h.Logger) - return - } - - dbsvc := h.Databases - if err = dbsvc.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, h.Logger) - return - } - - db := httprouter.ParamsFromContext(ctx).ByName("db") - res, err := h.allRPs(ctx, dbsvc, srcID, db) - if err != nil { - msg := fmt.Sprintf("unable to connect get RPs %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, h.Logger) - return - } - encodeJSON(w, http.StatusOK, res, h.Logger) -} - -func (h *Service) allRPs(ctx context.Context, dbsvc chronograf.Databases, srcID int, db string) ([]rpResponse, error) { - allRP, err := dbsvc.AllRP(ctx, db) - if err != nil { - return nil, err - } - - rps := make([]rpResponse, len(allRP)) - for i, rp := range allRP { - rp := rpResponse{ - Name: rp.Name, - Duration: rp.Duration, - Replication: rp.Replication, - ShardDuration: rp.ShardDuration, - Default: rp.Default, - } - rp.WithLinks(srcID, db) - rps[i] = rp - } - return rps, nil -} - -// NewRetentionPolicy creates a new retention policy for a database -func (h *Service) NewRetentionPolicy(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) - return - } - - src, err := h.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, h.Logger) - return - } - - dbsvc := h.Databases - if err = dbsvc.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, h.Logger) - return - } - - postedRP := &chronograf.RetentionPolicy{} - if err := json.NewDecoder(r.Body).Decode(postedRP); err != nil { - invalidJSON(w, h.Logger) - return - } - if err := ValidRetentionPolicyRequest(postedRP); err != nil { - invalidData(w, err, h.Logger) - return - } - - db := httprouter.ParamsFromContext(ctx).ByName("db") - rp, err := dbsvc.CreateRP(ctx, db, postedRP) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), h.Logger) - return - } - res := rpResponse{ - Name: rp.Name, - Duration: rp.Duration, - Replication: rp.Replication, - ShardDuration: rp.ShardDuration, - Default: rp.Default, - } - res.WithLinks(srcID, db) - encodeJSON(w, http.StatusCreated, res, h.Logger) -} - -// UpdateRetentionPolicy modifies an existing retention policy for a database -func (h *Service) UpdateRetentionPolicy(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) - return - } - - src, err := h.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, h.Logger) - return - } - - dbsvc := h.Databases - if err = dbsvc.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, h.Logger) - return - } - - postedRP := &chronograf.RetentionPolicy{} - if err := json.NewDecoder(r.Body).Decode(postedRP); err != nil { - invalidJSON(w, h.Logger) - return - } - if err := ValidRetentionPolicyRequest(postedRP); err != nil { - invalidData(w, err, h.Logger) - return - } - - params := httprouter.ParamsFromContext(ctx) - db := params.ByName("db") - rp := params.ByName("rp") - p, err := dbsvc.UpdateRP(ctx, db, rp, postedRP) - - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), h.Logger) - return - } - - res := rpResponse{ - Name: p.Name, - Duration: p.Duration, - Replication: p.Replication, - ShardDuration: p.ShardDuration, - Default: p.Default, - } - res.WithLinks(srcID, db) - encodeJSON(w, http.StatusCreated, res, h.Logger) -} - -// DropRetentionPolicy removes a retention policy from a database -func (s *Service) DropRetentionPolicy(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - src, err := s.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, s.Logger) - return - } - - dbsvc := s.Databases - if err = dbsvc.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - params := httprouter.ParamsFromContext(ctx) - db := params.ByName("db") - rp := params.ByName("rp") - dropErr := dbsvc.DropRP(ctx, db, rp) - if dropErr != nil { - Error(w, http.StatusBadRequest, dropErr.Error(), s.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -// Measurements lists measurements within a database -func (h *Service) Measurements(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) - return - } - - limit, offset, err := validMeasurementQuery(r.URL.Query()) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), h.Logger) - return - } - - src, err := h.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, h.Logger) - return - } - - dbsvc := h.Databases - if err = dbsvc.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, h.Logger) - return - } - - db := httprouter.ParamsFromContext(ctx).ByName("db") - measurements, err := dbsvc.GetMeasurements(ctx, db, limit, offset) - if err != nil { - msg := fmt.Sprintf("Unable to get measurements %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, h.Logger) - return - } - - res := measurementsResponse{ - Measurements: measurements, - Links: newMeasurementLinks(srcID, db, limit, offset), - } - - encodeJSON(w, http.StatusOK, res, h.Logger) -} - -func validMeasurementQuery(query url.Values) (limit, offset int, err error) { - limitParam := query.Get(limitQuery) - if limitParam == "" { - limit = 100 - } else { - limit, err = strconv.Atoi(limitParam) - if err != nil { - return - } - if limit <= 0 { - limit = 100 - } - } - - offsetParam := query.Get(offsetQuery) - if offsetParam == "" { - offset = 0 - } else { - offset, err = strconv.Atoi(offsetParam) - if err != nil { - return - } - if offset < 0 { - offset = 0 - } - } - - return -} - -// ValidDatabaseRequest checks if the database posted is valid -func ValidDatabaseRequest(d *chronograf.Database) error { - if len(d.Name) == 0 { - return fmt.Errorf("name is required") - } - return nil -} - -// ValidRetentionPolicyRequest checks if a retention policy is valid on POST -func ValidRetentionPolicyRequest(rp *chronograf.RetentionPolicy) error { - if len(rp.Name) == 0 { - return fmt.Errorf("name is required") - } - if len(rp.Duration) == 0 { - return fmt.Errorf("duration is required") - } - if rp.Replication == 0 { - return fmt.Errorf("replication factor is invalid") - } - return nil -} diff --git a/chronograf/server/databases_test.go b/chronograf/server/databases_test.go deleted file mode 100644 index 5372da0806b..00000000000 --- a/chronograf/server/databases_test.go +++ /dev/null @@ -1,648 +0,0 @@ -package server - -import ( - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -func TestService_GetDatabases(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - TimeSeriesClient TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - Databases chronograf.Databases - } - type args struct { - w http.ResponseWriter - r *http.Request - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - h := &Service{ - Store: &Store{ - SourcesStore: tt.fields.SourcesStore, - ServersStore: tt.fields.ServersStore, - LayoutsStore: tt.fields.LayoutsStore, - UsersStore: tt.fields.UsersStore, - DashboardsStore: tt.fields.DashboardsStore, - }, - TimeSeriesClient: tt.fields.TimeSeriesClient, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - Databases: tt.fields.Databases, - } - h.GetDatabases(tt.args.w, tt.args.r) - }) - } -} - -func TestService_NewDatabase(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - TimeSeriesClient TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - Databases chronograf.Databases - } - type args struct { - w http.ResponseWriter - r *http.Request - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - h := &Service{ - Store: &Store{ - SourcesStore: tt.fields.SourcesStore, - ServersStore: tt.fields.ServersStore, - LayoutsStore: tt.fields.LayoutsStore, - UsersStore: tt.fields.UsersStore, - DashboardsStore: tt.fields.DashboardsStore, - }, - TimeSeriesClient: tt.fields.TimeSeriesClient, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - Databases: tt.fields.Databases, - } - h.NewDatabase(tt.args.w, tt.args.r) - }) - } -} - -func TestService_DropDatabase(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - TimeSeriesClient TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - Databases chronograf.Databases - } - type args struct { - w http.ResponseWriter - r *http.Request - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - h := &Service{ - Store: &Store{ - SourcesStore: tt.fields.SourcesStore, - ServersStore: tt.fields.ServersStore, - LayoutsStore: tt.fields.LayoutsStore, - UsersStore: tt.fields.UsersStore, - DashboardsStore: tt.fields.DashboardsStore, - }, - TimeSeriesClient: tt.fields.TimeSeriesClient, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - Databases: tt.fields.Databases, - } - h.DropDatabase(tt.args.w, tt.args.r) - }) - } -} - -func TestService_RetentionPolicies(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - TimeSeriesClient TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - Databases chronograf.Databases - } - type args struct { - w http.ResponseWriter - r *http.Request - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - h := &Service{ - Store: &Store{ - SourcesStore: tt.fields.SourcesStore, - ServersStore: tt.fields.ServersStore, - LayoutsStore: tt.fields.LayoutsStore, - UsersStore: tt.fields.UsersStore, - DashboardsStore: tt.fields.DashboardsStore, - }, - TimeSeriesClient: tt.fields.TimeSeriesClient, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - Databases: tt.fields.Databases, - } - h.RetentionPolicies(tt.args.w, tt.args.r) - }) - } -} - -func TestService_NewRetentionPolicy(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - TimeSeriesClient TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - Databases chronograf.Databases - } - type args struct { - w http.ResponseWriter - r *http.Request - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - h := &Service{ - Store: &Store{ - SourcesStore: tt.fields.SourcesStore, - ServersStore: tt.fields.ServersStore, - LayoutsStore: tt.fields.LayoutsStore, - UsersStore: tt.fields.UsersStore, - DashboardsStore: tt.fields.DashboardsStore, - }, - TimeSeriesClient: tt.fields.TimeSeriesClient, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - Databases: tt.fields.Databases, - } - h.NewRetentionPolicy(tt.args.w, tt.args.r) - }) - } -} - -func TestService_UpdateRetentionPolicy(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - TimeSeriesClient TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - Databases chronograf.Databases - } - type args struct { - w http.ResponseWriter - r *http.Request - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - h := &Service{ - Store: &Store{ - SourcesStore: tt.fields.SourcesStore, - ServersStore: tt.fields.ServersStore, - LayoutsStore: tt.fields.LayoutsStore, - UsersStore: tt.fields.UsersStore, - DashboardsStore: tt.fields.DashboardsStore, - }, - TimeSeriesClient: tt.fields.TimeSeriesClient, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - Databases: tt.fields.Databases, - } - h.UpdateRetentionPolicy(tt.args.w, tt.args.r) - }) - } -} - -func TestService_DropRetentionPolicy(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - TimeSeriesClient TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - Databases chronograf.Databases - } - type args struct { - w http.ResponseWriter - r *http.Request - } - tests := []struct { - name string - fields fields - args args - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - h := &Service{ - Store: &Store{ - SourcesStore: tt.fields.SourcesStore, - ServersStore: tt.fields.ServersStore, - LayoutsStore: tt.fields.LayoutsStore, - UsersStore: tt.fields.UsersStore, - DashboardsStore: tt.fields.DashboardsStore, - }, - TimeSeriesClient: tt.fields.TimeSeriesClient, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - Databases: tt.fields.Databases, - } - h.DropRetentionPolicy(tt.args.w, tt.args.r) - }) - } -} - -func TestService_Measurements(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - Logger chronograf.Logger - Databases chronograf.Databases - } - type args struct { - queryParams map[string]string - } - type wants struct { - statusCode int - body string - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "Gets 100 measurements when no limit or offset provided", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, srcID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 0, - }, nil - }, - }, - Databases: &mocks.Databases{ - ConnectF: func(context.Context, *chronograf.Source) error { - return nil - }, - GetMeasurementsF: func(ctx context.Context, db string, limit, offset int) ([]chronograf.Measurement, error) { - return []chronograf.Measurement{ - { - Name: "pineapple", - }, - { - Name: "cubeapple", - }, - { - Name: "pinecube", - }, - }, nil - }, - }, - }, - args: args{ - queryParams: map[string]string{}, - }, - wants: wants{ - statusCode: 200, - body: `{"measurements":[{"name":"pineapple"},{"name":"cubeapple"},{"name":"pinecube"}],"links":{"self":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=100\u0026offset=0","first":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=100\u0026offset=0","next":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=100\u0026offset=100"}} -`, - }, - }, - { - name: "Fails when invalid limit value provided", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, srcID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 0, - }, nil - }, - }, - }, - args: args{ - queryParams: map[string]string{ - "limit": "joe", - }, - }, - wants: wants{ - statusCode: 422, - body: `{"code":422,"message":"strconv.Atoi: parsing \"joe\": invalid syntax"}`, - }, - }, - { - name: "Fails when invalid offset value provided", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, srcID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 0, - }, nil - }, - }, - }, - args: args{ - queryParams: map[string]string{ - "offset": "bob", - }, - }, - wants: wants{ - statusCode: 422, - body: `{"code":422,"message":"strconv.Atoi: parsing \"bob\": invalid syntax"}`, - }, - }, - { - name: "Overrides limit less than or equal to 0 with limit 100", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, srcID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 0, - }, nil - }, - }, - Databases: &mocks.Databases{ - ConnectF: func(context.Context, *chronograf.Source) error { - return nil - }, - GetMeasurementsF: func(ctx context.Context, db string, limit, offset int) ([]chronograf.Measurement, error) { - return []chronograf.Measurement{ - { - Name: "pineapple", - }, - { - Name: "cubeapple", - }, - { - Name: "pinecube", - }, - }, nil - }, - }, - }, - args: args{ - queryParams: map[string]string{ - "limit": "0", - }, - }, - wants: wants{ - statusCode: 200, - body: `{"measurements":[{"name":"pineapple"},{"name":"cubeapple"},{"name":"pinecube"}],"links":{"self":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=100\u0026offset=0","first":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=100\u0026offset=0","next":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=100\u0026offset=100"}} -`, - }, - }, - { - name: "Overrides offset less than 0 with offset 0", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, srcID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 0, - }, nil - }, - }, - Databases: &mocks.Databases{ - ConnectF: func(context.Context, *chronograf.Source) error { - return nil - }, - GetMeasurementsF: func(ctx context.Context, db string, limit, offset int) ([]chronograf.Measurement, error) { - return []chronograf.Measurement{ - { - Name: "pineapple", - }, - { - Name: "cubeapple", - }, - { - Name: "pinecube", - }, - }, nil - }, - }, - }, - args: args{ - queryParams: map[string]string{ - "offset": "-1337", - }, - }, - wants: wants{ - statusCode: 200, - body: `{"measurements":[{"name":"pineapple"},{"name":"cubeapple"},{"name":"pinecube"}],"links":{"self":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=100\u0026offset=0","first":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=100\u0026offset=0","next":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=100\u0026offset=100"}} -`, - }, - }, - { - name: "Provides a prev link when offset exceeds limit", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, srcID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 0, - }, nil - }, - }, - Databases: &mocks.Databases{ - ConnectF: func(context.Context, *chronograf.Source) error { - return nil - }, - GetMeasurementsF: func(ctx context.Context, db string, limit, offset int) ([]chronograf.Measurement, error) { - return []chronograf.Measurement{ - { - Name: "pineapple", - }, - { - Name: "cubeapple", - }, - { - Name: "pinecube", - }, - { - Name: "billietta", - }, - { - Name: "bobbetta", - }, - { - Name: "bobcube", - }, - }, nil - }, - }, - }, - args: args{ - queryParams: map[string]string{ - "limit": "2", - "offset": "4", - }, - }, - wants: wants{ - statusCode: 200, - body: `{"measurements":[{"name":"pineapple"},{"name":"cubeapple"},{"name":"pinecube"},{"name":"billietta"},{"name":"bobbetta"},{"name":"bobcube"}],"links":{"self":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=2\u0026offset=4","first":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=2\u0026offset=0","next":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=2\u0026offset=6","prev":"/chronograf/v1/sources/0/dbs/pineapples/measurements?limit=2\u0026offset=2"}} -`, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - logger := &chronograf.NoopLogger{} - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - Logger: logger, - Databases: tt.fields.Databases, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest( - "GET", - "http://any.url", - nil, - ) - r = r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: "0", - }, - { - Key: "db", - Value: "pineapples", - }, - })) - - q := r.URL.Query() - for key, value := range tt.args.queryParams { - q.Add(key, value) - } - r.URL.RawQuery = q.Encode() - - h.Measurements(w, r) - - resp := w.Result() - body, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() - - if err != nil { - t.Error("TestService_Measurements not able to retrieve body") - } - - var msmts measurementsResponse - if err := json.Unmarshal(body, &msmts); err != nil { - t.Error("TestService_Measurements not able to unmarshal JSON response") - } - - if tt.wants.statusCode != resp.StatusCode { - t.Errorf("%q. StatusCode:\nwant\n%v\ngot\n%v", tt.name, tt.wants.statusCode, resp.StatusCode) - } - - if tt.wants.body != string(body) { - t.Errorf("%q. Body:\nwant\n*%s*\ngot\n*%s*", tt.name, tt.wants.body, string(body)) - } - }) - } -} - -func TestValidDatabaseRequest(t *testing.T) { - type args struct { - d *chronograf.Database - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := ValidDatabaseRequest(tt.args.d); (err != nil) != tt.wantErr { - t.Errorf("ValidDatabaseRequest() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestValidRetentionPolicyRequest(t *testing.T) { - type args struct { - rp *chronograf.RetentionPolicy - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := ValidRetentionPolicyRequest(tt.args.rp); (err != nil) != tt.wantErr { - t.Errorf("ValidRetentionPolicyRequest() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/chronograf/server/env.go b/chronograf/server/env.go deleted file mode 100644 index 8573e77e0c9..00000000000 --- a/chronograf/server/env.go +++ /dev/null @@ -1,27 +0,0 @@ -package server - -import ( - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -type envResponse struct { - Links selfLinks `json:"links"` - TelegrafSystemInterval string `json:"telegrafSystemInterval"` -} - -func newEnvResponse(env chronograf.Environment) *envResponse { - return &envResponse{ - Links: selfLinks{ - Self: "/chronograf/v1/env", - }, - TelegrafSystemInterval: env.TelegrafSystemInterval.String(), - } -} - -// Environment retrieves the global application configuration -func (s *Service) Environment(w http.ResponseWriter, r *http.Request) { - res := newEnvResponse(s.Env) - encodeJSON(w, http.StatusOK, res, s.Logger) -} diff --git a/chronograf/server/env_test.go b/chronograf/server/env_test.go deleted file mode 100644 index 07a8ee88f75..00000000000 --- a/chronograf/server/env_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package server - -import ( - "io/ioutil" - "net/http/httptest" - "testing" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestEnvironment(t *testing.T) { - type fields struct { - Environment chronograf.Environment - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - wants wants - }{ - { - name: "Get environment", - fields: fields{ - Environment: chronograf.Environment{ - TelegrafSystemInterval: 1 * time.Minute, - }, - }, - wants: wants{ - statusCode: 200, - contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/env"},"telegrafSystemInterval":"1m0s"}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Env: tt.fields.Environment, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - - s.Environment(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. Config() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. Config() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. Config() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} diff --git a/chronograf/server/helpers.go b/chronograf/server/helpers.go deleted file mode 100644 index 89527349823..00000000000 --- a/chronograf/server/helpers.go +++ /dev/null @@ -1,7 +0,0 @@ -package server - -import "net/http" - -func location(w http.ResponseWriter, self string) { - w.Header().Add("Location", self) -} diff --git a/chronograf/server/hsts.go b/chronograf/server/hsts.go deleted file mode 100644 index 1b6f54d71aa..00000000000 --- a/chronograf/server/hsts.go +++ /dev/null @@ -1,12 +0,0 @@ -package server - -import "net/http" - -// HSTS add HTTP Strict Transport Security header with a max-age of two years -// Inspired from https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go -func HSTS(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains") - next.ServeHTTP(w, r) - }) -} diff --git a/chronograf/server/influx.go b/chronograf/server/influx.go deleted file mode 100644 index a8786879707..00000000000 --- a/chronograf/server/influx.go +++ /dev/null @@ -1,142 +0,0 @@ -package server - -import ( - "crypto/tls" - "encoding/json" - "fmt" - "net" - "net/http" - "net/http/httputil" - "net/url" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/influx" -) - -// ValidInfluxRequest checks if queries specify a command. -func ValidInfluxRequest(p chronograf.Query) error { - if p.Command == "" { - return fmt.Errorf("query field required") - } - return nil -} - -type postInfluxResponse struct { - Results interface{} `json:"results"` // results from influx -} - -// Influx proxies requests to influxdb. -func (s *Service) Influx(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - var req chronograf.Query - if err = json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - if err = ValidInfluxRequest(req); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - ts, err := s.TimeSeries(src) - if err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - if err = ts.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", id, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - response, err := ts.Query(ctx, req) - if err != nil { - if err == chronograf.ErrUpstreamTimeout { - msg := "Timeout waiting for Influx response" - Error(w, http.StatusRequestTimeout, msg, s.Logger) - return - } - // TODO: Here I want to return the error code from influx. - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := postInfluxResponse{ - Results: response, - } - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -func (s *Service) Write(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, id) - if err != nil { - notFound(w, id, s.Logger) - return - } - - u, err := url.Parse(src.URL) - if err != nil { - msg := fmt.Sprintf("Error parsing source url: %v", err) - Error(w, http.StatusUnprocessableEntity, msg, s.Logger) - return - } - u.Path = "/write" - u.RawQuery = r.URL.RawQuery - - director := func(req *http.Request) { - // Set the Host header of the original source URL - req.Host = u.Host - req.URL = u - // Because we are acting as a proxy, influxdb needs to have the - // basic auth or bearer token information set as a header directly - auth := influx.DefaultAuthorization(&src) - auth.Set(req) - } - - proxy := &httputil.ReverseProxy{ - Director: director, - } - - // The connection to influxdb is using a self-signed certificate. - // This modifies uses the same values as http.DefaultTransport but specifies - // InsecureSkipVerify - if src.InsecureSkipVerify { - proxy.Transport = &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - }).DialContext, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - } - - proxy.ServeHTTP(w, r) -} diff --git a/chronograf/server/influx_test.go b/chronograf/server/influx_test.go deleted file mode 100644 index 715fb6681e4..00000000000 --- a/chronograf/server/influx_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package server - -import ( - "bytes" - "context" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -func TestService_Influx(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - type want struct { - StatusCode int - ContentType string - Body string - } - tests := []struct { - name string - fields fields - args args - ID string - want want - }{ - { - name: "Proxies request to Influxdb", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1337, - URL: "http://any.url", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - QueryF: func(ctx context.Context, query chronograf.Query) (chronograf.Response, error) { - return mocks.NewResponse( - `{"results":[{"statement_id":0,"series":[{"name":"cpu","columns":["key","value"],"values":[["cpu","cpu-total"],["cpu","cpu0"],["cpu","cpu1"],["cpu","cpu2"],["cpu","cpu3"],["host","pineapples-MBP"],["host","pineapples-MacBook-Pro.local"]]}]}]}`, - nil, - ), - nil - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://any.url", - ioutil.NopCloser( - bytes.NewReader([]byte( - `{"db":"bob", "rp":"joe", "query":"SELECT mean(\"usage_user\") FROM cpu WHERE \"cpu\" = 'cpu-total' AND time > now() - 10m GROUP BY host;"}`, - )), - ), - ), - }, - ID: "1", - want: want{ - StatusCode: http.StatusOK, - ContentType: "application/json", - Body: `{"results":{"results":[{"statement_id":0,"series":[{"name":"cpu","columns":["key","value"],"values":[["cpu","cpu-total"],["cpu","cpu0"],["cpu","cpu1"],["cpu","cpu2"],["cpu","cpu3"],["host","pineapples-MBP"],["host","pineapples-MacBook-Pro.local"]]}]}]}} -`, - }, - }, - } - - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - } - h.Influx(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - contentType := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.want.StatusCode { - t.Errorf("%q. Influx() = got %v, want %v", tt.name, resp.StatusCode, tt.want.StatusCode) - } - if contentType != tt.want.ContentType { - t.Errorf("%q. Influx() = got %v, want %v", tt.name, contentType, tt.want.ContentType) - } - if string(body) != tt.want.Body { - t.Errorf("%q. Influx() =\ngot ***%v***\nwant ***%v***\n", tt.name, string(body), tt.want.Body) - } - - } -} diff --git a/chronograf/server/kapacitors.go b/chronograf/server/kapacitors.go deleted file mode 100644 index 3fc09da3a7b..00000000000 --- a/chronograf/server/kapacitors.go +++ /dev/null @@ -1,791 +0,0 @@ -package server - -// TODO(desa): resolve kapacitor dependency - -//type postKapacitorRequest struct { -// Name *string `json:"name"` // User facing name of kapacitor instance.; Required: true -// URL *string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092);/ Required: true -// Username string `json:"username,omitempty"` // Username for authentication to kapacitor -// Password string `json:"password,omitempty"` -// InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted. -// Active bool `json:"active"` -// Organization string `json:"organization"` // Organization is the organization ID that resource belongs to -//} -// -//func (p *postKapacitorRequest) Valid(defaultOrgID string) error { -// if p.Name == nil || p.URL == nil { -// return fmt.Errorf("name and url required") -// } -// -// if p.Organization == "" { -// p.Organization = defaultOrgID -// } -// -// url, err := url.ParseRequestURI(*p.URL) -// if err != nil { -// return fmt.Errorf("invalid source URI: %v", err) -// } -// if len(url.Scheme) == 0 { -// return fmt.Errorf("invalid URL; no URL scheme defined") -// } -// -// return nil -//} -// -//type kapaLinks struct { -// Proxy string `json:"proxy"` // URL location of proxy endpoint for this source -// Self string `json:"self"` // Self link mapping to this resource -// Rules string `json:"rules"` // Rules link for defining roles alerts for kapacitor -// Tasks string `json:"tasks"` // Tasks link to define a task against the proxy -// Ping string `json:"ping"` // Ping path to kapacitor -//} -// -//type kapacitor struct { -// ID int `json:"id,string"` // Unique identifier representing a kapacitor instance. -// Name string `json:"name"` // User facing name of kapacitor instance. -// URL string `json:"url"` // URL for the kapacitor backend (e.g. http://localhost:9092) -// Username string `json:"username,omitempty"` // Username for authentication to kapacitor -// Password string `json:"password,omitempty"` -// InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted. -// Active bool `json:"active"` -// Links kapaLinks `json:"links"` // Links are URI locations related to kapacitor -//} -// -//// NewKapacitor adds valid kapacitor store store. -//func (s *Service) NewKapacitor(w http.ResponseWriter, r *http.Request) { -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// _, err = s.Store.Sources(ctx).Get(ctx, srcID) -// if err != nil { -// notFound(w, srcID, s.Logger) -// return -// } -// -// var req postKapacitorRequest -// if err = json.NewDecoder(r.Body).Decode(&req); err != nil { -// invalidJSON(w, s.Logger) -// return -// } -// -// defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) -// if err != nil { -// unknownErrorWithMessage(w, err, s.Logger) -// return -// } -// -// if err := req.Valid(defaultOrg.ID); err != nil { -// invalidData(w, err, s.Logger) -// return -// } -// -// srv := chronograf.Server{ -// SrcID: srcID, -// Name: *req.Name, -// Username: req.Username, -// Password: req.Password, -// InsecureSkipVerify: req.InsecureSkipVerify, -// URL: *req.URL, -// Active: req.Active, -// Organization: req.Organization, -// } -// -// if srv, err = s.Store.Servers(ctx).Add(ctx, srv); err != nil { -// msg := fmt.Errorf("error storing kapacitor %v: %v", req, err) -// unknownErrorWithMessage(w, msg, s.Logger) -// return -// } -// -// res := newKapacitor(srv) -// location(w, res.Links.Self) -// encodeJSON(w, http.StatusCreated, res, s.Logger) -//} -// -//func newKapacitor(srv chronograf.Server) kapacitor { -// httpAPISrcs := "/chronograf/v1/sources" -// return kapacitor{ -// ID: srv.ID, -// Name: srv.Name, -// Username: srv.Username, -// URL: srv.URL, -// Active: srv.Active, -// InsecureSkipVerify: srv.InsecureSkipVerify, -// Links: kapaLinks{ -// Self: fmt.Sprintf("%s/%d/kapacitors/%d", httpAPISrcs, srv.SrcID, srv.ID), -// Proxy: fmt.Sprintf("%s/%d/kapacitors/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID), -// Rules: fmt.Sprintf("%s/%d/kapacitors/%d/rules", httpAPISrcs, srv.SrcID, srv.ID), -// Tasks: fmt.Sprintf("%s/%d/kapacitors/%d/proxy?path=/kapacitor/v1/tasks", httpAPISrcs, srv.SrcID, srv.ID), -// Ping: fmt.Sprintf("%s/%d/kapacitors/%d/proxy?path=/kapacitor/v1/ping", httpAPISrcs, srv.SrcID, srv.ID), -// }, -// } -//} -// -//type kapacitors struct { -// Kapacitors []kapacitor `json:"kapacitors"` -//} -// -//// Kapacitors retrieves all kapacitors from store. -//func (s *Service) Kapacitors(w http.ResponseWriter, r *http.Request) { -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// mrSrvs, err := s.Store.Servers(ctx).All(ctx) -// if err != nil { -// Error(w, http.StatusInternalServerError, "Error loading kapacitors", s.Logger) -// return -// } -// -// srvs := []kapacitor{} -// for _, srv := range mrSrvs { -// if srv.SrcID == srcID && srv.Type == "" { -// srvs = append(srvs, newKapacitor(srv)) -// } -// } -// -// res := kapacitors{ -// Kapacitors: srvs, -// } -// -// encodeJSON(w, http.StatusOK, res, s.Logger) -//} -// -//// KapacitorsID retrieves a kapacitor with ID from store. -//func (s *Service) KapacitorsID(w http.ResponseWriter, r *http.Request) { -// id, err := paramID("kid", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// srv, err := s.Store.Servers(ctx).Get(ctx, id) -// if err != nil || srv.SrcID != srcID || srv.Type != "" { -// notFound(w, id, s.Logger) -// return -// } -// -// res := newKapacitor(srv) -// encodeJSON(w, http.StatusOK, res, s.Logger) -//} -// -//// RemoveKapacitor deletes kapacitor from store. -//func (s *Service) RemoveKapacitor(w http.ResponseWriter, r *http.Request) { -// id, err := paramID("kid", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// srv, err := s.Store.Servers(ctx).Get(ctx, id) -// if err != nil || srv.SrcID != srcID || srv.Type != "" { -// notFound(w, id, s.Logger) -// return -// } -// -// if err = s.Store.Servers(ctx).Delete(ctx, srv); err != nil { -// unknownErrorWithMessage(w, err, s.Logger) -// return -// } -// -// w.WriteHeader(http.StatusNoContent) -//} -// -//type patchKapacitorRequest struct { -// Name *string `json:"name,omitempty"` // User facing name of kapacitor instance. -// URL *string `json:"url,omitempty"` // URL for the kapacitor -// Username *string `json:"username,omitempty"` // Username for kapacitor auth -// Password *string `json:"password,omitempty"` -// InsecureSkipVerify *bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the kapacitor is accepted. -// Active *bool `json:"active"` -//} -// -//func (p *patchKapacitorRequest) Valid() error { -// if p.URL != nil { -// url, err := url.ParseRequestURI(*p.URL) -// if err != nil { -// return fmt.Errorf("invalid source URI: %v", err) -// } -// if len(url.Scheme) == 0 { -// return fmt.Errorf("invalid URL; no URL scheme defined") -// } -// } -// return nil -//} -// -//// UpdateKapacitor incrementally updates a kapacitor definition in the store -//func (s *Service) UpdateKapacitor(w http.ResponseWriter, r *http.Request) { -// id, err := paramID("kid", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// srv, err := s.Store.Servers(ctx).Get(ctx, id) -// if err != nil || srv.SrcID != srcID || srv.Type != "" { -// notFound(w, id, s.Logger) -// return -// } -// -// var req patchKapacitorRequest -// if err := json.NewDecoder(r.Body).Decode(&req); err != nil { -// invalidJSON(w, s.Logger) -// return -// } -// -// if err := req.Valid(); err != nil { -// invalidData(w, err, s.Logger) -// return -// } -// -// if req.Name != nil { -// srv.Name = *req.Name -// } -// if req.URL != nil { -// srv.URL = *req.URL -// } -// if req.Password != nil { -// srv.Password = *req.Password -// } -// if req.Username != nil { -// srv.Username = *req.Username -// } -// if req.InsecureSkipVerify != nil { -// srv.InsecureSkipVerify = *req.InsecureSkipVerify -// } -// if req.Active != nil { -// srv.Active = *req.Active -// } -// -// if err := s.Store.Servers(ctx).Update(ctx, srv); err != nil { -// msg := fmt.Sprintf("Error updating kapacitor ID %d", id) -// Error(w, http.StatusInternalServerError, msg, s.Logger) -// return -// } -// -// res := newKapacitor(srv) -// encodeJSON(w, http.StatusOK, res, s.Logger) -//} -// -//// KapacitorRulesPost proxies POST to kapacitor -//func (s *Service) KapacitorRulesPost(w http.ResponseWriter, r *http.Request) { -// id, err := paramID("kid", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// srv, err := s.Store.Servers(ctx).Get(ctx, id) -// if err != nil || srv.SrcID != srcID { -// notFound(w, id, s.Logger) -// return -// } -// -// c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) -// -// var req chronograf.AlertRule -// if err = json.NewDecoder(r.Body).Decode(&req); err != nil { -// invalidData(w, err, s.Logger) -// return -// } -// // TODO: validate this data -// /* -// if err := req.Valid(); err != nil { -// invalidData(w, err) -// return -// } -// */ -// -// if req.Name == "" { -// req.Name = req.ID -// } -// -// req.ID = "" -// task, err := c.Create(ctx, req) -// if err != nil { -// invalidData(w, err, s.Logger) -// return -// } -// res := newAlertResponse(task, srv.SrcID, srv.ID) -// location(w, res.Links.Self) -// encodeJSON(w, http.StatusCreated, res, s.Logger) -//} -// -//type alertLinks struct { -// Self string `json:"self"` -// Kapacitor string `json:"kapacitor"` -// Output string `json:"output"` -//} -// -//type alertResponse struct { -// chronograf.AlertRule -// Links alertLinks `json:"links"` -//} -// -//// newAlertResponse formats task into an alertResponse -//func newAlertResponse(task *kapa.Task, srcID, kapaID int) *alertResponse { -// res := &alertResponse{ -// AlertRule: task.Rule, -// Links: alertLinks{ -// Self: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/rules/%s", srcID, kapaID, task.ID), -// Kapacitor: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srcID, kapaID, url.QueryEscape(task.Href)), -// Output: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srcID, kapaID, url.QueryEscape(task.HrefOutput)), -// }, -// } -// -// if res.AlertNodes.Alerta == nil { -// res.AlertNodes.Alerta = []*chronograf.Alerta{} -// } -// -// for i, a := range res.AlertNodes.Alerta { -// if a.Service == nil { -// a.Service = []string{} -// res.AlertNodes.Alerta[i] = a -// } -// } -// -// if res.AlertNodes.Email == nil { -// res.AlertNodes.Email = []*chronograf.Email{} -// } -// -// for i, a := range res.AlertNodes.Email { -// if a.To == nil { -// a.To = []string{} -// res.AlertNodes.Email[i] = a -// } -// } -// -// if res.AlertNodes.Exec == nil { -// res.AlertNodes.Exec = []*chronograf.Exec{} -// } -// -// for i, a := range res.AlertNodes.Exec { -// if a.Command == nil { -// a.Command = []string{} -// res.AlertNodes.Exec[i] = a -// } -// } -// -// if res.AlertNodes.HipChat == nil { -// res.AlertNodes.HipChat = []*chronograf.HipChat{} -// } -// -// if res.AlertNodes.Kafka == nil { -// res.AlertNodes.Kafka = []*chronograf.Kafka{} -// } -// -// if res.AlertNodes.Log == nil { -// res.AlertNodes.Log = []*chronograf.Log{} -// } -// -// if res.AlertNodes.OpsGenie == nil { -// res.AlertNodes.OpsGenie = []*chronograf.OpsGenie{} -// } -// -// for i, a := range res.AlertNodes.OpsGenie { -// if a.Teams == nil { -// a.Teams = []string{} -// res.AlertNodes.OpsGenie[i] = a -// } -// -// if a.Recipients == nil { -// a.Recipients = []string{} -// res.AlertNodes.OpsGenie[i] = a -// } -// } -// -// if res.AlertNodes.OpsGenie2 == nil { -// res.AlertNodes.OpsGenie2 = []*chronograf.OpsGenie{} -// } -// -// for i, a := range res.AlertNodes.OpsGenie2 { -// if a.Teams == nil { -// a.Teams = []string{} -// res.AlertNodes.OpsGenie2[i] = a -// } -// -// if a.Recipients == nil { -// a.Recipients = []string{} -// res.AlertNodes.OpsGenie2[i] = a -// } -// } -// -// if res.AlertNodes.PagerDuty == nil { -// res.AlertNodes.PagerDuty = []*chronograf.PagerDuty{} -// } -// -// if res.AlertNodes.PagerDuty2 == nil { -// res.AlertNodes.PagerDuty2 = []*chronograf.PagerDuty{} -// } -// -// if res.AlertNodes.Posts == nil { -// res.AlertNodes.Posts = []*chronograf.Post{} -// } -// -// for i, a := range res.AlertNodes.Posts { -// if a.Headers == nil { -// a.Headers = map[string]string{} -// res.AlertNodes.Posts[i] = a -// } -// } -// -// if res.AlertNodes.Pushover == nil { -// res.AlertNodes.Pushover = []*chronograf.Pushover{} -// } -// -// if res.AlertNodes.Sensu == nil { -// res.AlertNodes.Sensu = []*chronograf.Sensu{} -// } -// -// for i, a := range res.AlertNodes.Sensu { -// if a.Handlers == nil { -// a.Handlers = []string{} -// res.AlertNodes.Sensu[i] = a -// } -// } -// -// if res.AlertNodes.Slack == nil { -// res.AlertNodes.Slack = []*chronograf.Slack{} -// } -// -// if res.AlertNodes.Talk == nil { -// res.AlertNodes.Talk = []*chronograf.Talk{} -// } -// -// if res.AlertNodes.TCPs == nil { -// res.AlertNodes.TCPs = []*chronograf.TCP{} -// } -// -// if res.AlertNodes.Telegram == nil { -// res.AlertNodes.Telegram = []*chronograf.Telegram{} -// } -// -// if res.AlertNodes.VictorOps == nil { -// res.AlertNodes.VictorOps = []*chronograf.VictorOps{} -// } -// -// if res.Query != nil { -// if res.Query.ID == "" { -// res.Query.ID = res.ID -// } -// -// if res.Query.Fields == nil { -// res.Query.Fields = make([]chronograf.Field, 0) -// } -// -// if res.Query.GroupBy.Tags == nil { -// res.Query.GroupBy.Tags = make([]string, 0) -// } -// -// if res.Query.Tags == nil { -// res.Query.Tags = make(map[string][]string) -// } -// } -// return res -//} -// -//// ValidRuleRequest checks if the requested rule change is valid -//func ValidRuleRequest(rule chronograf.AlertRule) error { -// if rule.Query == nil { -// return fmt.Errorf("invalid alert rule: no query defined") -// } -// var hasFuncs bool -// for _, f := range rule.Query.Fields { -// if f.Type == "func" && len(f.Args) > 0 { -// hasFuncs = true -// } -// } -// // All kapacitor rules with functions must have a window that is applied -// // every amount of time -// if rule.Every == "" && hasFuncs { -// return fmt.Errorf(`invalid alert rule: functions require an "every" window`) -// } -// return nil -//} -// -//// KapacitorRulesPut proxies PATCH to kapacitor -//func (s *Service) KapacitorRulesPut(w http.ResponseWriter, r *http.Request) { -// id, err := paramID("kid", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// srv, err := s.Store.Servers(ctx).Get(ctx, id) -// if err != nil || srv.SrcID != srcID { -// notFound(w, id, s.Logger) -// return -// } -// -// tid := httprouter.GetParamFromContext(ctx, "tid") -// c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) -// var req chronograf.AlertRule -// if err = json.NewDecoder(r.Body).Decode(&req); err != nil { -// invalidData(w, err, s.Logger) -// return -// } -// // TODO: validate this data -// /* -// if err := req.Valid(); err != nil { -// invalidData(w, err) -// return -// } -// */ -// -// // Check if the rule exists and is scoped correctly -// if _, err = c.Get(ctx, tid); err != nil { -// if err == chronograf.ErrAlertNotFound { -// notFound(w, id, s.Logger) -// return -// } -// Error(w, http.StatusInternalServerError, err.Error(), s.Logger) -// return -// } -// -// // Replace alert completely with this new alert. -// req.ID = tid -// task, err := c.Update(ctx, c.Href(tid), req) -// if err != nil { -// invalidData(w, err, s.Logger) -// return -// } -// res := newAlertResponse(task, srv.SrcID, srv.ID) -// encodeJSON(w, http.StatusOK, res, s.Logger) -//} -// -//// KapacitorStatus is the current state of a running task -//type KapacitorStatus struct { -// Status string `json:"status"` -//} -// -//// Valid check if the kapacitor status is enabled or disabled -//func (k *KapacitorStatus) Valid() error { -// if k.Status == "enabled" || k.Status == "disabled" { -// return nil -// } -// return fmt.Errorf("invalid Kapacitor status: %s", k.Status) -//} -// -//// KapacitorRulesStatus proxies PATCH to kapacitor to enable/disable tasks -//func (s *Service) KapacitorRulesStatus(w http.ResponseWriter, r *http.Request) { -// id, err := paramID("kid", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// srv, err := s.Store.Servers(ctx).Get(ctx, id) -// if err != nil || srv.SrcID != srcID { -// notFound(w, id, s.Logger) -// return -// } -// -// tid := httprouter.GetParamFromContext(ctx, "tid") -// c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) -// -// var req KapacitorStatus -// if err = json.NewDecoder(r.Body).Decode(&req); err != nil { -// invalidJSON(w, s.Logger) -// return -// } -// if err := req.Valid(); err != nil { -// invalidData(w, err, s.Logger) -// return -// } -// -// // Check if the rule exists and is scoped correctly -// _, err = c.Get(ctx, tid) -// if err != nil { -// if err == chronograf.ErrAlertNotFound { -// notFound(w, id, s.Logger) -// return -// } -// Error(w, http.StatusInternalServerError, err.Error(), s.Logger) -// return -// } -// -// var task *kapa.Task -// if req.Status == "enabled" { -// task, err = c.Enable(ctx, c.Href(tid)) -// } else { -// task, err = c.Disable(ctx, c.Href(tid)) -// } -// -// if err != nil { -// Error(w, http.StatusInternalServerError, err.Error(), s.Logger) -// return -// } -// -// res := newAlertResponse(task, srv.SrcID, srv.ID) -// encodeJSON(w, http.StatusOK, res, s.Logger) -//} -// -//// KapacitorRulesGet retrieves all rules -//func (s *Service) KapacitorRulesGet(w http.ResponseWriter, r *http.Request) { -// id, err := paramID("kid", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// srv, err := s.Store.Servers(ctx).Get(ctx, id) -// if err != nil || srv.SrcID != srcID { -// notFound(w, id, s.Logger) -// return -// } -// -// c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) -// tasks, err := c.All(ctx) -// if err != nil { -// Error(w, http.StatusInternalServerError, err.Error(), s.Logger) -// return -// } -// -// res := allAlertsResponse{ -// Rules: []*alertResponse{}, -// } -// for _, task := range tasks { -// ar := newAlertResponse(task, srv.SrcID, srv.ID) -// res.Rules = append(res.Rules, ar) -// } -// encodeJSON(w, http.StatusOK, res, s.Logger) -//} -// -//type allAlertsResponse struct { -// Rules []*alertResponse `json:"rules"` -//} -// -//// KapacitorRulesID retrieves specific task -//func (s *Service) KapacitorRulesID(w http.ResponseWriter, r *http.Request) { -// id, err := paramID("kid", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// srv, err := s.Store.Servers(ctx).Get(ctx, id) -// if err != nil || srv.SrcID != srcID { -// notFound(w, id, s.Logger) -// return -// } -// tid := httprouter.GetParamFromContext(ctx, "tid") -// -// c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) -// -// // Check if the rule exists within scope -// task, err := c.Get(ctx, tid) -// if err != nil { -// if err == chronograf.ErrAlertNotFound { -// notFound(w, id, s.Logger) -// return -// } -// Error(w, http.StatusInternalServerError, err.Error(), s.Logger) -// return -// } -// -// res := newAlertResponse(task, srv.SrcID, srv.ID) -// encodeJSON(w, http.StatusOK, res, s.Logger) -//} -// -//// KapacitorRulesDelete proxies DELETE to kapacitor -//func (s *Service) KapacitorRulesDelete(w http.ResponseWriter, r *http.Request) { -// id, err := paramID("kid", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// srcID, err := paramID("id", r) -// if err != nil { -// Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) -// return -// } -// -// ctx := r.Context() -// srv, err := s.Store.Servers(ctx).Get(ctx, id) -// if err != nil || srv.SrcID != srcID { -// notFound(w, id, s.Logger) -// return -// } -// -// c := kapa.NewClient(srv.URL, srv.Username, srv.Password, srv.InsecureSkipVerify) -// -// tid := httprouter.GetParamFromContext(ctx, "tid") -// // Check if the rule is linked to this server and kapacitor -// if _, err := c.Get(ctx, tid); err != nil { -// if err == chronograf.ErrAlertNotFound { -// notFound(w, id, s.Logger) -// return -// } -// Error(w, http.StatusInternalServerError, err.Error(), s.Logger) -// return -// } -// if err := c.Delete(ctx, c.Href(tid)); err != nil { -// Error(w, http.StatusInternalServerError, err.Error(), s.Logger) -// return -// } -// -// w.WriteHeader(http.StatusNoContent) -//} diff --git a/chronograf/server/kapacitors_test.go b/chronograf/server/kapacitors_test.go deleted file mode 100644 index 59f544b3fc4..00000000000 --- a/chronograf/server/kapacitors_test.go +++ /dev/null @@ -1,265 +0,0 @@ -package server_test - -//const tickScript = ` -//stream -// |from() -// .measurement('cpu') -// |alert() -// .crit(lambda: "usage_idle" < 10) -// .log('/tmp/alert') -//` -// -//func TestValidRuleRequest(t *testing.T) { -// tests := []struct { -// name string -// rule chronograf.AlertRule -// wantErr bool -// }{ -// { -// name: "No every with functions", -// rule: chronograf.AlertRule{ -// Query: &chronograf.QueryConfig{ -// Fields: []chronograf.Field{ -// { -// Value: "max", -// Type: "func", -// Args: []chronograf.Field{ -// { -// Value: "oldmanpeabody", -// Type: "field", -// }, -// }, -// }, -// }, -// }, -// }, -// wantErr: true, -// }, -// { -// name: "With every", -// rule: chronograf.AlertRule{ -// Every: "10s", -// Query: &chronograf.QueryConfig{ -// Fields: []chronograf.Field{ -// { -// Value: "max", -// Type: "func", -// Args: []chronograf.Field{ -// { -// Value: "oldmanpeabody", -// Type: "field", -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// { -// name: "No query config", -// rule: chronograf.AlertRule{}, -// wantErr: true, -// }, -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// if err := server.ValidRuleRequest(tt.rule); (err != nil) != tt.wantErr { -// t.Errorf("ValidRuleRequest() error = %v, wantErr %v", err, tt.wantErr) -// } -// }) -// } -//} -// -//func Test_KapacitorRulesGet(t *testing.T) { -// kapaTests := []struct { -// name string -// requestPath string -// mockAlerts []chronograf.AlertRule -// expected []chronograf.AlertRule -// }{ -// { -// name: "basic", -// requestPath: "/chronograf/v1/sources/1/kapacitors/1/rules", -// mockAlerts: []chronograf.AlertRule{ -// { -// ID: "cpu_alert", -// Name: "cpu_alert", -// Status: "enabled", -// Type: "stream", -// DBRPs: []chronograf.DBRP{{DB: "telegraf", RP: "autogen"}}, -// TICKScript: tickScript, -// }, -// }, -// expected: []chronograf.AlertRule{ -// { -// ID: "cpu_alert", -// Name: "cpu_alert", -// Status: "enabled", -// Type: "stream", -// DBRPs: []chronograf.DBRP{{DB: "telegraf", RP: "autogen"}}, -// TICKScript: tickScript, -// AlertNodes: chronograf.AlertNodes{ -// Posts: []*chronograf.Post{}, -// TCPs: []*chronograf.TCP{}, -// Email: []*chronograf.Email{}, -// Exec: []*chronograf.Exec{}, -// Log: []*chronograf.Log{}, -// VictorOps: []*chronograf.VictorOps{}, -// PagerDuty: []*chronograf.PagerDuty{}, -// PagerDuty2: []*chronograf.PagerDuty{}, -// Pushover: []*chronograf.Pushover{}, -// Sensu: []*chronograf.Sensu{}, -// Slack: []*chronograf.Slack{}, -// Telegram: []*chronograf.Telegram{}, -// HipChat: []*chronograf.HipChat{}, -// Alerta: []*chronograf.Alerta{}, -// OpsGenie: []*chronograf.OpsGenie{}, -// OpsGenie2: []*chronograf.OpsGenie{}, -// Talk: []*chronograf.Talk{}, -// Kafka: []*chronograf.Kafka{}, -// }, -// }, -// }, -// }, -// } -// -// for _, test := range kapaTests { -// test := test // needed to avoid data race -// t.Run(test.name, func(t *testing.T) { -// t.Parallel() -// -// // setup mock kapa API -// kapaSrv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { -// params := r.URL.Query() -// limit, err := strconv.Atoi(params.Get("limit")) -// if err != nil { -// rw.WriteHeader(http.StatusBadRequest) -// return -// } -// offset, err := strconv.Atoi(params.Get("offset")) -// if err != nil { -// rw.WriteHeader(http.StatusBadRequest) -// return -// } -// -// tsks := []map[string]interface{}{} -// for _, task := range test.mockAlerts { -// tsks = append(tsks, map[string]interface{}{ -// "id": task.ID, -// "script": tickScript, -// "status": "enabled", -// "type": "stream", -// "dbrps": []chronograf.DBRP{ -// { -// DB: "telegraf", -// RP: "autogen", -// }, -// }, -// "link": map[string]interface{}{ -// "rel": "self", -// "href": "/kapacitor/v1/tasks/cpu_alert", -// }, -// }) -// } -// -// var tasks map[string]interface{} -// -// if offset >= len(tsks) { -// tasks = map[string]interface{}{ -// "tasks": []map[string]interface{}{}, -// } -// } else if limit+offset > len(tsks) { -// tasks = map[string]interface{}{ -// "tasks": tsks[offset:], -// } -// } -// //} else { -// //tasks = map[string]interface{}{ -// //"tasks": tsks[offset : offset+limit], -// //} -// //} -// -// err = json.NewEncoder(rw).Encode(&tasks) -// if err != nil { -// t.Error("Failed to encode JSON. err:", err) -// } -// })) -// defer kapaSrv.Close() -// -// // setup mock service and test logger -// testLogger := mocks.TestLogger{} -// svc := &server.Service{ -// Store: &mocks.Store{ -// SourcesStore: &mocks.SourcesStore{ -// GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { -// return chronograf.Source{ -// ID: ID, -// InsecureSkipVerify: true, -// }, nil -// }, -// }, -// ServersStore: &mocks.ServersStore{ -// GetF: func(ctx context.Context, ID int) (chronograf.Server, error) { -// return chronograf.Server{ -// SrcID: ID, -// URL: kapaSrv.URL, -// }, nil -// }, -// }, -// }, -// Logger: &testLogger, -// } -// -// // setup request and response recorder -// req := httptest.NewRequest("GET", test.requestPath, strings.NewReader("")) -// rr := httptest.NewRecorder() -// -// // setup context and request params -// bg := context.Background() -// params := httprouter.Params{ -// { -// Key: "id", -// Value: "1", -// }, -// { -// Key: "kid", -// Value: "1", -// }, -// } -// ctx := httprouter.WithParams(bg, params) -// req = req.WithContext(ctx) -// -// // invoke KapacitorRulesGet endpoint -// svc.KapacitorRulesGet(rr, req) -// -// // destructure response -// frame := struct { -// Rules []struct { -// chronograf.AlertRule -// Links json.RawMessage `json:"links"` -// } `json:"rules"` -// }{} -// -// resp := rr.Result() -// -// err := json.NewDecoder(resp.Body).Decode(&frame) -// if err != nil { -// t.Fatal("Err decoding kapa rule response: err:", err) -// } -// -// actual := make([]chronograf.AlertRule, len(frame.Rules)) -// -// for i := range frame.Rules { -// actual[i] = frame.Rules[i].AlertRule -// } -// -// if resp.StatusCode != http.StatusOK { -// t.Fatal("Expected HTTP 200 OK but got", resp.Status) -// } -// -// if !cmp.Equal(test.expected, actual) { -// t.Fatalf("%q - Alert rules differ! diff:\n%s\n", test.name, cmp.Diff(test.expected, actual)) -// } -// }) -// } -//} diff --git a/chronograf/server/layout.go b/chronograf/server/layout.go deleted file mode 100644 index 9c3ad03277b..00000000000 --- a/chronograf/server/layout.go +++ /dev/null @@ -1,119 +0,0 @@ -package server - -import ( - "fmt" - "net/http" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" -) - -type link struct { - Href string `json:"href"` - Rel string `json:"rel"` -} - -type layoutResponse struct { - chronograf.Layout - Link link `json:"link"` -} - -func newLayoutResponse(layout chronograf.Layout) layoutResponse { - httpAPILayouts := "/chronograf/v1/layouts" - href := fmt.Sprintf("%s/%s", httpAPILayouts, layout.ID) - rel := "self" - - for idx, cell := range layout.Cells { - axes := []string{"x", "y", "y2"} - - if cell.Axes == nil { - layout.Cells[idx].Axes = make(map[string]chronograf.Axis, len(axes)) - } - - if cell.CellColors == nil { - layout.Cells[idx].CellColors = []chronograf.CellColor{} - } - - for _, axis := range axes { - if _, found := cell.Axes[axis]; !found { - layout.Cells[idx].Axes[axis] = chronograf.Axis{ - Bounds: []string{}, - } - } - } - } - - return layoutResponse{ - Layout: layout, - Link: link{ - Href: href, - Rel: rel, - }, - } -} - -type getLayoutsResponse struct { - Layouts []layoutResponse `json:"layouts"` -} - -// Layouts retrieves all layouts from store -func (s *Service) Layouts(w http.ResponseWriter, r *http.Request) { - // Construct a filter sieve for both applications and measurements - filtered := map[string]bool{} - for _, a := range r.URL.Query()["app"] { - filtered[a] = true - } - - for _, m := range r.URL.Query()["measurement"] { - filtered[m] = true - } - - ctx := r.Context() - layouts, err := s.Store.Layouts(ctx).All(ctx) - if err != nil { - Error(w, http.StatusInternalServerError, "Error loading layouts", s.Logger) - return - } - - filter := func(layout *chronograf.Layout) bool { - // If the length of the filter is zero then all values are acceptable. - if len(filtered) == 0 { - return true - } - - // If filter contains either measurement or application - return filtered[layout.Measurement] || filtered[layout.Application] - } - - res := getLayoutsResponse{ - Layouts: []layoutResponse{}, - } - - seen := make(map[string]bool) - for _, layout := range layouts { - // remove duplicates - if seen[layout.Measurement+layout.ID] { - continue - } - // filter for data that belongs to provided application or measurement - if filter(&layout) { - res.Layouts = append(res.Layouts, newLayoutResponse(layout)) - } - } - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// LayoutsID retrieves layout with ID from store -func (s *Service) LayoutsID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id := httprouter.GetParamFromContext(ctx, "id") - - layout, err := s.Store.Layouts(ctx).Get(ctx, id) - if err != nil { - Error(w, http.StatusNotFound, fmt.Sprintf("ID %s not found", id), s.Logger) - return - } - - res := newLayoutResponse(layout) - encodeJSON(w, http.StatusOK, res, s.Logger) -} diff --git a/chronograf/server/layout_test.go b/chronograf/server/layout_test.go deleted file mode 100644 index c2c4103703e..00000000000 --- a/chronograf/server/layout_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package server_test - -import ( - "context" - "encoding/json" - "net/http/httptest" - "net/url" - "strings" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/server" -) - -func Test_Layouts(t *testing.T) { - layoutTests := []struct { - name string - expected chronograf.Layout - allLayouts []chronograf.Layout - focusedApp string // should filter all layouts to this app only - shouldErr bool - }{ - { - "empty layout", - chronograf.Layout{}, - []chronograf.Layout{}, - "", - false, - }, - { - "several layouts", - chronograf.Layout{ - ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c", - Application: "influxdb", - Measurement: "influxdb", - }, - []chronograf.Layout{ - chronograf.Layout{ - ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c", - Application: "influxdb", - Measurement: "influxdb", - }, - }, - "", - false, - }, - { - "filtered app", - chronograf.Layout{ - ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c", - Application: "influxdb", - Measurement: "influxdb", - }, - []chronograf.Layout{ - chronograf.Layout{ - ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c", - Application: "influxdb", - Measurement: "influxdb", - }, - chronograf.Layout{ - ID: "b020101b-ea6b-4c8c-9f0e-db0ba501f4ef", - Application: "chronograf", - Measurement: "chronograf", - }, - }, - "influxdb", - false, - }, - { - "axis zero values", - chronograf.Layout{ - ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c", - Application: "influxdb", - Measurement: "influxdb", - Cells: []chronograf.Cell{ - { - X: 0, - Y: 0, - W: 4, - H: 4, - I: "3b0e646b-2ca3-4df2-95a5-fd80915459dd", - Name: "A Graph", - CellColors: []chronograf.CellColor{}, - Axes: map[string]chronograf.Axis{ - "x": chronograf.Axis{ - Bounds: []string{}, - }, - "y": chronograf.Axis{ - Bounds: []string{}, - }, - "y2": chronograf.Axis{ - Bounds: []string{}, - }, - }, - }, - }, - }, - []chronograf.Layout{ - chronograf.Layout{ - ID: "d20a21c8-69f1-4780-90fe-e69f5e4d138c", - Application: "influxdb", - Measurement: "influxdb", - Cells: []chronograf.Cell{ - { - X: 0, - Y: 0, - W: 4, - H: 4, - I: "3b0e646b-2ca3-4df2-95a5-fd80915459dd", - CellColors: []chronograf.CellColor{}, - Name: "A Graph", - }, - }, - }, - }, - "", - false, - }, - } - - for _, test := range layoutTests { - test := test - t.Run(test.name, func(t *testing.T) { - t.Parallel() - - // setup mock chronograf.Service and mock logger - lg := &mocks.TestLogger{} - svc := server.Service{ - Store: &mocks.Store{LayoutsStore: &mocks.LayoutsStore{ - AllF: func(ctx context.Context) ([]chronograf.Layout, error) { - if len(test.allLayouts) == 0 { - return []chronograf.Layout{ - test.expected, - }, nil - } else { - return test.allLayouts, nil - } - }, - }, - }, - Logger: lg, - } - - // setup mock request and response - rr := httptest.NewRecorder() - reqURL := url.URL{ - Path: "/chronograf/v1/layouts", - } - params := reqURL.Query() - - // add query params required by test - if test.focusedApp != "" { - params.Add("app", test.focusedApp) - } - - // re-inject query params - reqURL.RawQuery = params.Encode() - - req := httptest.NewRequest("GET", reqURL.RequestURI(), strings.NewReader("")) - - // invoke handler for layouts endpoint - svc.Layouts(rr, req) - - // create a throwaway frame to unwrap Layouts - respFrame := struct { - Layouts []struct { - chronograf.Layout - Link interface{} `json:"-"` - } `json:"layouts"` - }{} - - // decode resp into respFrame - resp := rr.Result() - if err := json.NewDecoder(resp.Body).Decode(&respFrame); err != nil { - t.Fatalf("%q - Error unmarshalling JSON: err: %s", test.name, err.Error()) - } - - // compare actual and expected - if !cmp.Equal(test.expected, respFrame.Layouts[0].Layout) { - t.Fatalf("%q - Expected layouts to be equal: diff:\n\t%s", test.name, cmp.Diff(test.expected, respFrame.Layouts[0].Layout)) - } - }) - } -} diff --git a/chronograf/server/links.go b/chronograf/server/links.go deleted file mode 100644 index acfdfd7cf50..00000000000 --- a/chronograf/server/links.go +++ /dev/null @@ -1,59 +0,0 @@ -package server - -import ( - "errors" - "net/url" -) - -type getFluxLinksResponse struct { - AST string `json:"ast"` - Self string `json:"self"` - Suggestions string `json:"suggestions"` -} - -type getConfigLinksResponse struct { - Self string `json:"self"` // Location of the whole global application configuration - Auth string `json:"auth"` // Location of the auth section of the global application configuration -} - -type getOrganizationConfigLinksResponse struct { - Self string `json:"self"` // Location of the organization configuration - LogViewer string `json:"logViewer"` // Location of the organization-specific log viewer configuration -} - -type getExternalLinksResponse struct { - StatusFeed *string `json:"statusFeed,omitempty"` // Location of the a JSON Feed for client's Status page News Feed - CustomLinks []CustomLink `json:"custom,omitempty"` // Any custom external links for client's User menu -} - -// CustomLink is a handler that returns a custom link to be used in server's routes response, within ExternalLinks -type CustomLink struct { - Name string `json:"name"` - URL string `json:"url"` -} - -// NewCustomLinks transforms `--custom-link` CLI flag data or `CUSTOM_LINKS` ENV -// var data into a data structure that the Chronograf client will expect -func NewCustomLinks(links map[string]string) ([]CustomLink, error) { - customLinks := make([]CustomLink, 0, len(links)) - for name, link := range links { - if name == "" { - return nil, errors.New("customLink missing key for Name") - } - if link == "" { - return nil, errors.New("customLink missing value for URL") - } - _, err := url.Parse(link) - if err != nil { - return nil, err - } - - customLink := CustomLink{ - Name: name, - URL: link, - } - customLinks = append(customLinks, customLink) - } - - return customLinks, nil -} diff --git a/chronograf/server/links_test.go b/chronograf/server/links_test.go deleted file mode 100644 index 0ff6835fd6b..00000000000 --- a/chronograf/server/links_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package server - -import ( - "reflect" - "testing" -) - -func TestNewCustomLinks(t *testing.T) { - tests := []struct { - name string - args map[string]string - want []CustomLink - wantErr bool - }{ - { - name: "Unknown error in NewCustomLinks", - args: map[string]string{ - "cubeapple": "https://cube.apple", - }, - want: []CustomLink{ - { - Name: "cubeapple", - URL: "https://cube.apple", - }, - }, - }, - { - name: "CustomLink missing Name", - args: map[string]string{ - "": "https://cube.apple", - }, - wantErr: true, - }, - { - name: "CustomLink missing URL", - args: map[string]string{ - "cubeapple": "", - }, - wantErr: true, - }, - { - name: "Missing protocol scheme", - args: map[string]string{ - "cubeapple": ":k%8a#", - }, - wantErr: true, - }, - } - - for _, tt := range tests { - got, err := NewCustomLinks(tt.args) - if (err != nil) != tt.wantErr { - t.Errorf("%q. NewCustomLinks() error = %v, wantErr %v", tt.name, err, tt.wantErr) - continue - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%q. NewCustomLinks() = %v, want %v", tt.name, got, tt.want) - } - } -} diff --git a/chronograf/server/logger.go b/chronograf/server/logger.go deleted file mode 100644 index cb88bf06066..00000000000 --- a/chronograf/server/logger.go +++ /dev/null @@ -1,63 +0,0 @@ -package server - -import ( - "net/http" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// statusWriterFlusher captures the status header of an http.ResponseWriter -// and is a flusher -type statusWriter struct { - http.ResponseWriter - Flusher http.Flusher - status int -} - -func (w *statusWriter) WriteHeader(status int) { - w.status = status - w.ResponseWriter.WriteHeader(status) -} - -func (w *statusWriter) Status() int { return w.status } - -// Flush is here because the underlying HTTP chunked transfer response writer -// to implement http.Flusher. Without it data is silently buffered. This -// was discovered when proxying kapacitor chunked logs. -func (w *statusWriter) Flush() { - if w.Flusher != nil { - w.Flusher.Flush() - } -} - -// Logger is middleware that logs the request -func Logger(logger chronograf.Logger, next http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - now := time.Now() - logger.WithField("component", "server"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("url", r.URL). - Debug("Request") - - sw := &statusWriter{ - ResponseWriter: w, - } - if f, ok := w.(http.Flusher); ok { - sw.Flusher = f - } - next.ServeHTTP(sw, r) - later := time.Now() - elapsed := later.Sub(now) - - logger. - WithField("component", "server"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("response_time", elapsed.String()). - WithField("status", sw.Status()). - Info("Response: ", http.StatusText(sw.Status())) - } - return http.HandlerFunc(fn) -} diff --git a/chronograf/server/logout.go b/chronograf/server/logout.go deleted file mode 100644 index dd7c2cabe47..00000000000 --- a/chronograf/server/logout.go +++ /dev/null @@ -1,24 +0,0 @@ -package server - -import ( - "net/http" - "path" -) - -// Logout chooses the correct provider logout route and redirects to it -func Logout(nextURL, basepath string, routes AuthRoutes) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - principal, err := getPrincipal(ctx) - if err != nil { - http.Redirect(w, r, path.Join(basepath, nextURL), http.StatusTemporaryRedirect) - return - } - route, ok := routes.Lookup(principal.Issuer) - if !ok { - http.Redirect(w, r, path.Join(basepath, nextURL), http.StatusTemporaryRedirect) - return - } - http.Redirect(w, r, route.Logout, http.StatusTemporaryRedirect) - } -} diff --git a/chronograf/server/mapping.go b/chronograf/server/mapping.go deleted file mode 100644 index 134e9f6a8fa..00000000000 --- a/chronograf/server/mapping.go +++ /dev/null @@ -1,264 +0,0 @@ -package server - -import ( - "context" - - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -func (s *Service) mapPrincipalToSuperAdmin(p oauth2.Principal) bool { - if p.Issuer != "auth0" { - return false - } - - groups := strings.Split(p.Group, ",") - superAdmin := false - for _, group := range groups { - if group != "" && group == s.SuperAdminProviderGroups.auth0 { - superAdmin = true - break - } - } - return superAdmin -} - -func (s *Service) mapPrincipalToRoles(ctx context.Context, p oauth2.Principal) ([]chronograf.Role, error) { - mappings, err := s.Store.Mappings(ctx).All(ctx) - if err != nil { - return nil, err - } - roles := []chronograf.Role{} -MappingsLoop: - for _, mapping := range mappings { - if applyMapping(mapping, p) { - org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &mapping.Organization}) - if err != nil { - continue MappingsLoop - } - - for _, role := range roles { - if role.Organization == org.ID { - continue MappingsLoop - } - } - roles = append(roles, chronograf.Role{Organization: org.ID, Name: org.DefaultRole}) - } - } - - return roles, nil -} - -func applyMapping(m chronograf.Mapping, p oauth2.Principal) bool { - switch m.Provider { - case chronograf.MappingWildcard, p.Issuer: - default: - return false - } - - switch m.Scheme { - case chronograf.MappingWildcard, "oauth2": - default: - return false - } - - if m.ProviderOrganization == chronograf.MappingWildcard { - return true - } - - groups := strings.Split(p.Group, ",") - - return matchGroup(m.ProviderOrganization, groups) -} - -func matchGroup(match string, groups []string) bool { - for _, group := range groups { - if match == group { - return true - } - } - - return false -} - -type mappingsRequest chronograf.Mapping - -// Valid determines if a mapping request is valid -func (m *mappingsRequest) Valid() error { - if m.Provider == "" { - return fmt.Errorf("mapping must specify provider") - } - if m.Scheme == "" { - return fmt.Errorf("mapping must specify scheme") - } - if m.ProviderOrganization == "" { - return fmt.Errorf("mapping must specify group") - } - - return nil -} - -type mappingResponse struct { - Links selfLinks `json:"links"` - chronograf.Mapping -} - -func newMappingResponse(m chronograf.Mapping) *mappingResponse { - - return &mappingResponse{ - Links: selfLinks{ - Self: fmt.Sprintf("/chronograf/v1/mappings/%s", m.ID), - }, - Mapping: m, - } -} - -type mappingsResponse struct { - Links selfLinks `json:"links"` - Mappings []*mappingResponse `json:"mappings"` -} - -func newMappingsResponse(ms []chronograf.Mapping) *mappingsResponse { - mappings := []*mappingResponse{} - for _, m := range ms { - mappings = append(mappings, newMappingResponse(m)) - } - return &mappingsResponse{ - Links: selfLinks{ - Self: "/chronograf/v1/mappings", - }, - Mappings: mappings, - } -} - -// Mappings retrieves all mappings -func (s *Service) Mappings(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - mappings, err := s.Store.Mappings(ctx).All(ctx) - if err != nil { - Error(w, http.StatusInternalServerError, "failed to retrieve mappings from database", s.Logger) - return - } - - res := newMappingsResponse(mappings) - - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// NewMapping adds a new mapping -func (s *Service) NewMapping(w http.ResponseWriter, r *http.Request) { - var req mappingsRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := req.Valid(); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - - // validate that the organization exists - if !s.organizationExists(ctx, req.Organization) { - invalidData(w, fmt.Errorf("organization does not exist"), s.Logger) - return - } - - mapping := &chronograf.Mapping{ - Organization: req.Organization, - Scheme: req.Scheme, - Provider: req.Provider, - ProviderOrganization: req.ProviderOrganization, - } - - m, err := s.Store.Mappings(ctx).Add(ctx, mapping) - if err != nil { - Error(w, http.StatusInternalServerError, "failed to add mapping to database", s.Logger) - return - } - - cu := newMappingResponse(*m) - location(w, cu.Links.Self) - encodeJSON(w, http.StatusCreated, cu, s.Logger) -} - -// UpdateMapping updates a mapping -func (s *Service) UpdateMapping(w http.ResponseWriter, r *http.Request) { - var req mappingsRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := req.Valid(); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - - // validate that the organization exists - if !s.organizationExists(ctx, req.Organization) { - invalidData(w, fmt.Errorf("organization does not exist"), s.Logger) - return - } - - mapping := &chronograf.Mapping{ - ID: req.ID, - Organization: req.Organization, - Scheme: req.Scheme, - Provider: req.Provider, - ProviderOrganization: req.ProviderOrganization, - } - - err := s.Store.Mappings(ctx).Update(ctx, mapping) - if err != nil { - Error(w, http.StatusInternalServerError, "failed to update mapping in database", s.Logger) - return - } - - cu := newMappingResponse(*mapping) - location(w, cu.Links.Self) - encodeJSON(w, http.StatusOK, cu, s.Logger) -} - -// RemoveMapping removes a mapping -func (s *Service) RemoveMapping(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id := httprouter.GetParamFromContext(ctx, "id") - - m, err := s.Store.Mappings(ctx).Get(ctx, id) - if err == chronograf.ErrMappingNotFound { - Error(w, http.StatusNotFound, err.Error(), s.Logger) - return - } - - if err != nil { - Error(w, http.StatusInternalServerError, "failed to retrieve mapping from database", s.Logger) - return - } - - if err := s.Store.Mappings(ctx).Delete(ctx, m); err != nil { - Error(w, http.StatusInternalServerError, "failed to remove mapping from database", s.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -func (s *Service) organizationExists(ctx context.Context, orgID string) bool { - if _, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &orgID}); err != nil { - return false - } - - return true -} diff --git a/chronograf/server/mapping_test.go b/chronograf/server/mapping_test.go deleted file mode 100644 index c2f026e193c..00000000000 --- a/chronograf/server/mapping_test.go +++ /dev/null @@ -1,356 +0,0 @@ -package server - -import ( - "bytes" - "context" - "encoding/json" - "io/ioutil" - "net/http/httptest" - "testing" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -func TestMappings_All(t *testing.T) { - type fields struct { - MappingsStore chronograf.MappingsStore - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - wants wants - }{ - { - name: "get all mappings", - fields: fields{ - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{ - { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - }, - }, nil - }, - }, - }, - wants: wants{ - statusCode: 200, - contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/mappings"},"mappings":[{"links":{"self":"/chronograf/v1/mappings/"},"id":"","organizationId":"0","provider":"*","scheme":"*","providerOrganization":"*"}]}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - MappingsStore: tt.fields.MappingsStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - s.Mappings(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. Mappings() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. Mappings() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. Mappings() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} - -func TestMappings_Add(t *testing.T) { - type fields struct { - MappingsStore chronograf.MappingsStore - OrganizationsStore chronograf.OrganizationsStore - } - type args struct { - mapping *chronograf.Mapping - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "create new mapping", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - }, - MappingsStore: &mocks.MappingsStore{ - AddF: func(ctx context.Context, m *chronograf.Mapping) (*chronograf.Mapping, error) { - m.ID = "0" - return m, nil - }, - }, - }, - args: args{ - mapping: &chronograf.Mapping{ - Organization: "0", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - }, - wants: wants{ - statusCode: 201, - contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/mappings/0"},"id":"0","organizationId":"0","provider":"*","scheme":"*","providerOrganization":"*"}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - MappingsStore: tt.fields.MappingsStore, - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - - buf, _ := json.Marshal(tt.args.mapping) - r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - - s.NewMapping(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. Add() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. Add() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. Add() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} - -func TestMappings_Update(t *testing.T) { - type fields struct { - MappingsStore chronograf.MappingsStore - OrganizationsStore chronograf.OrganizationsStore - } - type args struct { - mapping *chronograf.Mapping - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "update new mapping", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - }, - MappingsStore: &mocks.MappingsStore{ - UpdateF: func(ctx context.Context, m *chronograf.Mapping) error { - return nil - }, - }, - }, - args: args{ - mapping: &chronograf.Mapping{ - ID: "1", - Organization: "0", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, - }, - wants: wants{ - statusCode: 200, - contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/mappings/1"},"id":"1","organizationId":"0","provider":"*","scheme":"*","providerOrganization":"*"}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - MappingsStore: tt.fields.MappingsStore, - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - - buf, _ := json.Marshal(tt.args.mapping) - r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - r = r.WithContext(httprouter.WithParams( - context.Background(), - httprouter.Params{ - { - Key: "id", - Value: tt.args.mapping.ID, - }, - })) - - s.UpdateMapping(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. Add() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. Add() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. Add() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} - -func TestMappings_Remove(t *testing.T) { - type fields struct { - MappingsStore chronograf.MappingsStore - } - type args struct { - id string - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "remove mapping", - fields: fields{ - MappingsStore: &mocks.MappingsStore{ - GetF: func(ctx context.Context, id string) (*chronograf.Mapping, error) { - return &chronograf.Mapping{ - ID: "1", - Organization: "0", - Provider: "*", - Scheme: "*", - ProviderOrganization: "*", - }, nil - }, - DeleteF: func(ctx context.Context, m *chronograf.Mapping) error { - return nil - }, - }, - }, - args: args{}, - wants: wants{ - statusCode: 204, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - MappingsStore: tt.fields.MappingsStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - - r = r.WithContext(httprouter.WithParams( - context.Background(), - httprouter.Params{ - { - Key: "id", - Value: tt.args.id, - }, - })) - - s.RemoveMapping(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. Remove() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. Remove() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. Remove() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} diff --git a/chronograf/server/me.go b/chronograf/server/me.go deleted file mode 100644 index 6a1f6d6601a..00000000000 --- a/chronograf/server/me.go +++ /dev/null @@ -1,400 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - "sort" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" - "github.com/influxdata/influxdb/v2/chronograf/organizations" - "golang.org/x/net/context" -) - -type meLinks struct { - Self string `json:"self"` // Self link mapping to this resource -} - -type meResponse struct { - *chronograf.User - Links meLinks `json:"links"` - Organizations []chronograf.Organization `json:"organizations"` - CurrentOrganization *chronograf.Organization `json:"currentOrganization,omitempty"` -} - -type noAuthMeResponse struct { - Links meLinks `json:"links"` -} - -func newNoAuthMeResponse() noAuthMeResponse { - return noAuthMeResponse{ - Links: meLinks{ - Self: "/chronograf/v1/me", - }, - } -} - -// If new user response is nil, return an empty meResponse because it -// indicates authentication is not needed -func newMeResponse(usr *chronograf.User, org string) meResponse { - base := "/chronograf/v1" - name := "me" - if usr != nil { - base = fmt.Sprintf("/chronograf/v1/organizations/%s/users", org) - name = PathEscape(fmt.Sprintf("%d", usr.ID)) - } - - return meResponse{ - User: usr, - Links: meLinks{ - Self: fmt.Sprintf("%s/%s", base, name), - }, - } -} - -// TODO: This Scheme value is hard-coded temporarily since we only currently -// support OAuth2. This hard-coding should be removed whenever we add -// support for other authentication schemes. -func getScheme(ctx context.Context) (string, error) { - return "oauth2", nil -} - -func getPrincipal(ctx context.Context) (oauth2.Principal, error) { - principal, ok := ctx.Value(oauth2.PrincipalKey).(oauth2.Principal) - if !ok { - return oauth2.Principal{}, fmt.Errorf("token not found") - } - - return principal, nil -} - -func getValidPrincipal(ctx context.Context) (oauth2.Principal, error) { - p, err := getPrincipal(ctx) - if err != nil { - return p, err - } - if p.Subject == "" { - return oauth2.Principal{}, fmt.Errorf("token not found") - } - if p.Issuer == "" { - return oauth2.Principal{}, fmt.Errorf("token not found") - } - return p, nil -} - -type meRequest struct { - // Organization is the OrganizationID - Organization string `json:"organization"` -} - -// UpdateMe changes the user's current organization on the JWT and responds -// with the same semantics as Me -func (s *Service) UpdateMe(auth oauth2.Authenticator) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - serverCtx := serverContext(ctx) - principal, err := auth.Validate(ctx, r) - if err != nil { - s.Logger.Error(fmt.Sprintf("Invalid principal: %v", err)) - Error(w, http.StatusForbidden, "invalid principal", s.Logger) - return - } - var req meRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - // validate that the organization exists - org, err := s.Store.Organizations(serverCtx).Get(serverCtx, chronograf.OrganizationQuery{ID: &req.Organization}) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - // validate that user belongs to organization - ctx = context.WithValue(ctx, organizations.ContextKey, req.Organization) - - p, err := getValidPrincipal(ctx) - if err != nil { - invalidData(w, err, s.Logger) - return - } - if p.Organization == "" { - defaultOrg, err := s.Store.Organizations(serverCtx).DefaultOrganization(serverCtx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - p.Organization = defaultOrg.ID - } - scheme, err := getScheme(ctx) - if err != nil { - invalidData(w, err, s.Logger) - return - } - _, err = s.Store.Users(ctx).Get(ctx, chronograf.UserQuery{ - Name: &p.Subject, - Provider: &p.Issuer, - Scheme: &scheme, - }) - if err == chronograf.ErrUserNotFound { - // If the user was not found, check to see if they are a super admin. If - // they are, add them to the organization. - u, err := s.Store.Users(serverCtx).Get(serverCtx, chronograf.UserQuery{ - Name: &p.Subject, - Provider: &p.Issuer, - Scheme: &scheme, - }) - if err != nil { - Error(w, http.StatusForbidden, err.Error(), s.Logger) - return - } - - if !u.SuperAdmin { - // Since a user is not a part of this organization and not a super admin, - // we should tell them that they are Forbidden (403) from accessing this resource - Error(w, http.StatusForbidden, chronograf.ErrUserNotFound.Error(), s.Logger) - return - } - - // If the user is a super admin give them an admin role in the - // requested organization. - u.Roles = append(u.Roles, chronograf.Role{ - Organization: org.ID, - Name: org.DefaultRole, - }) - if err := s.Store.Users(serverCtx).Update(serverCtx, u); err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - } else if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - // TODO: change to principal.CurrentOrganization - principal.Organization = req.Organization - - if err := auth.Authorize(ctx, w, principal); err != nil { - Error(w, http.StatusInternalServerError, err.Error(), s.Logger) - return - } - - ctx = context.WithValue(ctx, oauth2.PrincipalKey, principal) - - s.Me(w, r.WithContext(ctx)) - } -} - -// Me does a findOrCreate based on the username in the context -func (s *Service) Me(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - if !s.UseAuth { - // If there's no authentication, return an empty user - res := newNoAuthMeResponse() - encodeJSON(w, http.StatusOK, res, s.Logger) - return - } - - p, err := getValidPrincipal(ctx) - if err != nil { - invalidData(w, err, s.Logger) - return - } - scheme, err := getScheme(ctx) - if err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx = context.WithValue(ctx, organizations.ContextKey, p.Organization) - serverCtx := serverContext(ctx) - - defaultOrg, err := s.Store.Organizations(serverCtx).DefaultOrganization(serverCtx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - if p.Organization == "" { - p.Organization = defaultOrg.ID - } - - usr, err := s.Store.Users(serverCtx).Get(serverCtx, chronograf.UserQuery{ - Name: &p.Subject, - Provider: &p.Issuer, - Scheme: &scheme, - }) - if err != nil && err != chronograf.ErrUserNotFound { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - // user exists - if usr != nil { - superAdmin := s.mapPrincipalToSuperAdmin(p) - if superAdmin && !usr.SuperAdmin { - usr.SuperAdmin = superAdmin - err := s.Store.Users(serverCtx).Update(serverCtx, usr) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - } - - currentOrg, err := s.Store.Organizations(serverCtx).Get(serverCtx, chronograf.OrganizationQuery{ID: &p.Organization}) - if err == chronograf.ErrOrganizationNotFound { - // The intent is to force a the user to go through another auth flow - Error(w, http.StatusForbidden, "user's current organization was not found", s.Logger) - return - } - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - orgs, err := s.usersOrganizations(serverCtx, usr) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - res := newMeResponse(usr, currentOrg.ID) - res.Organizations = orgs - res.CurrentOrganization = currentOrg - encodeJSON(w, http.StatusOK, res, s.Logger) - return - } - - // Because we didnt find a user, making a new one - user := &chronograf.User{ - Name: p.Subject, - Provider: p.Issuer, - // TODO: This Scheme value is hard-coded temporarily since we only currently - // support OAuth2. This hard-coding should be removed whenever we add - // support for other authentication schemes. - Scheme: scheme, - // TODO(desa): this needs a better name - SuperAdmin: s.newUsersAreSuperAdmin(), - } - - superAdmin := s.mapPrincipalToSuperAdmin(p) - if superAdmin { - user.SuperAdmin = superAdmin - } - - roles, err := s.mapPrincipalToRoles(serverCtx, p) - if err != nil { - Error(w, http.StatusInternalServerError, err.Error(), s.Logger) - return - } - - if !superAdmin && len(roles) == 0 { - Error(w, http.StatusForbidden, "This Chronograf is private. To gain access, you must be explicitly added by an administrator.", s.Logger) - return - } - - // If the user is a superadmin, give them a role in the default organization - if user.SuperAdmin { - hasDefaultOrgRole := false - for _, role := range roles { - if role.Organization == defaultOrg.ID { - hasDefaultOrgRole = true - break - } - } - if !hasDefaultOrgRole { - roles = append(roles, chronograf.Role{ - Name: defaultOrg.DefaultRole, - Organization: defaultOrg.ID, - }) - } - } - - user.Roles = roles - - newUser, err := s.Store.Users(serverCtx).Add(serverCtx, user) - if err != nil { - msg := fmt.Errorf("error storing user %s: %v", user.Name, err) - unknownErrorWithMessage(w, msg, s.Logger) - return - } - - orgs, err := s.usersOrganizations(serverCtx, newUser) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - currentOrg, err := s.Store.Organizations(serverCtx).Get(serverCtx, chronograf.OrganizationQuery{ID: &p.Organization}) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - res := newMeResponse(newUser, currentOrg.ID) - res.Organizations = orgs - res.CurrentOrganization = currentOrg - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -func (s *Service) firstUser() bool { - serverCtx := serverContext(context.Background()) - numUsers, err := s.Store.Users(serverCtx).Num(serverCtx) - if err != nil { - return false - } - - return numUsers == 0 -} -func (s *Service) newUsersAreSuperAdmin() bool { - // It's not necessary to enforce that the first user is superAdmin here, since - // superAdminNewUsers defaults to true, but there's nothing else in the - // application that dictates that it must be true. - // So for that reason, we kept this here for now. We've discussed the - // future possibility of allowing users to override default values via CLI and - // this case could possibly happen then. - if s.firstUser() { - return true - } - serverCtx := serverContext(context.Background()) - cfg, err := s.Store.Config(serverCtx).Get(serverCtx) - if err != nil { - return false - } - return cfg.Auth.SuperAdminNewUsers -} - -func (s *Service) usersOrganizations(ctx context.Context, u *chronograf.User) ([]chronograf.Organization, error) { - if u == nil { - // TODO(desa): better error - return nil, fmt.Errorf("user was nil") - } - - orgIDs := map[string]bool{} - for _, role := range u.Roles { - orgIDs[role.Organization] = true - } - - orgs := []chronograf.Organization{} - for orgID := range orgIDs { - org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &orgID}) - - // There can be race conditions between deleting a organization and the me query - if err == chronograf.ErrOrganizationNotFound { - continue - } - - // Any other error should cause an error to be returned - if err != nil { - return nil, err - } - orgs = append(orgs, *org) - } - - sort.Slice(orgs, func(i, j int) bool { - return orgs[i].ID < orgs[j].ID - }) - - return orgs, nil -} diff --git a/chronograf/server/me_test.go b/chronograf/server/me_test.go deleted file mode 100644 index 8848b477ed7..00000000000 --- a/chronograf/server/me_test.go +++ /dev/null @@ -1,1455 +0,0 @@ -package server - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -func TestService_Me(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - OrganizationsStore chronograf.OrganizationsStore - MappingsStore chronograf.MappingsStore - ConfigStore chronograf.ConfigStore - SuperAdminProviderGroups superAdminProviderGroups - Logger chronograf.Logger - UseAuth bool - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - principal oauth2.Principal - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Existing user - not member of any organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{ - { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "0": - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.ViewerRoleName, - }, nil - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - }, nil - } - return nil, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - Name: "me", - Provider: "github", - Scheme: "oauth2", - }, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "me", - Issuer: "github", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"me","roles":null,"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer"}}`, - }, - { - name: "Existing superadmin - not member of any organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{}, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "0": - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.ViewerRoleName, - }, nil - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "The Bad Place", - }, nil - } - return nil, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - Name: "me", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - }, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "me", - Issuer: "github", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"me","roles":null,"provider":"github","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[],"currentOrganization":{"id":"0","name":"Default","defaultRole":"viewer"}}`, - }, - { - name: "Existing user - organization doesn't exist", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{}, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "0": - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.ViewerRoleName, - }, nil - } - return nil, chronograf.ErrOrganizationNotFound - }, - }, - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - Name: "me", - Provider: "github", - Scheme: "oauth2", - }, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "me", - Issuer: "github", - Organization: "1", - }, - wantStatus: http.StatusForbidden, - wantContentType: "application/json", - wantBody: `{"code":403,"message":"user's current organization was not found"}`, - }, - { - name: "default mapping applies to new user", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: true, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{ - { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - AllF: func(ctx context.Context) ([]chronograf.Organization, error) { - return []chronograf.Organization{ - chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"secret","superAdmin":true,"roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer"}}`, - }, - { - name: "New user - New users not super admin, not first user", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{ - { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - AllF: func(ctx context.Context) ([]chronograf.Organization, error) { - return []chronograf.Organization{ - chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer"}}`, - }, - { - name: "New user - New users not super admin, first user", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{ - { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - AllF: func(ctx context.Context) ([]chronograf.Organization, error) { - return []chronograf.Organization{ - chronograf.Organization{ - ID: "0", - Name: "The Gnarly Default", - DefaultRole: roles.ViewerRoleName, - }, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 0, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"secret","superAdmin":true,"roles":[{"name":"viewer","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Gnarly Default","defaultRole":"viewer"}],"currentOrganization":{"id":"0","name":"The Gnarly Default","defaultRole":"viewer"}}`, - }, - { - name: "Error adding user", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{}, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - }, nil - }, - AllF: func(ctx context.Context) ([]chronograf.Organization, error) { - return []chronograf.Organization{ - chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.ViewerRoleName, - }, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return nil, fmt.Errorf("why Heavy?") - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "heroku", - }, - wantStatus: http.StatusForbidden, - wantContentType: "application/json", - wantBody: `{"code":403,"message":"This Chronograf is private. To gain access, you must be explicitly added by an administrator."}`, - }, - { - name: "No Auth", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: false, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/me"}}`, - }, - { - name: "Empty Principal", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - Logger: &chronograf.NoopLogger{}, - }, - wantStatus: http.StatusUnprocessableEntity, - principal: oauth2.Principal{ - Subject: "", - Issuer: "", - }, - }, - { - name: "new user - Chronograf is private", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - ConfigStore: mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{}, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - }, - wantStatus: http.StatusForbidden, - wantContentType: "application/json", - wantBody: `{"code":403,"message":"This Chronograf is private. To gain access, you must be explicitly added by an administrator."}`, - }, - { - name: "new user - Chronograf is private, user is in auth0 superadmin group", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - SuperAdminProviderGroups: superAdminProviderGroups{ - auth0: "example", - }, - Logger: &chronograf.NoopLogger{}, - ConfigStore: mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{}, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - Group: "not_example,example", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"member","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Bad Place","defaultRole":"member"}],"currentOrganization":{"id":"0","name":"The Bad Place","defaultRole":"member"}}`, - }, - { - name: "new user - Chronograf is private, user is not in auth0 superadmin group", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - SuperAdminProviderGroups: superAdminProviderGroups{ - auth0: "example", - }, - Logger: &chronograf.NoopLogger{}, - ConfigStore: mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{}, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - Group: "not_example", - }, - wantStatus: http.StatusForbidden, - wantContentType: "application/json", - wantBody: `{"code":403,"message":"This Chronograf is private. To gain access, you must be explicitly added by an administrator."}`, - }, - { - name: "new user - Chronograf is not private, user is in auth0 superadmin group", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - SuperAdminProviderGroups: superAdminProviderGroups{ - auth0: "example", - }, - Logger: &chronograf.NoopLogger{}, - ConfigStore: mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{ - { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - Group: "example", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"member","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Bad Place","defaultRole":"member"}],"currentOrganization":{"id":"0","name":"The Bad Place","defaultRole":"member"}}`, - }, - { - name: "new user - Chronograf is not private (has a fully open wildcard mapping to an org), user is not in auth0 superadmin group", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - SuperAdminProviderGroups: superAdminProviderGroups{ - auth0: "example", - }, - Logger: &chronograf.NoopLogger{}, - ConfigStore: mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{ - { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return nil, chronograf.ErrUserNotFound - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - Group: "not_example", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"member","organization":"0"}],"provider":"auth0","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Bad Place","defaultRole":"member"}],"currentOrganization":{"id":"0","name":"The Bad Place","defaultRole":"member"}}`, - }, - { - name: "Existing user - Chronograf is not private, user doesn't have SuperAdmin status, user is in auth0 superadmin group", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - SuperAdminProviderGroups: superAdminProviderGroups{ - auth0: "example", - }, - Logger: &chronograf.NoopLogger{}, - ConfigStore: mocks.ConfigStore{ - Config: &chronograf.Config{}, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{ - { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - Name: "secret", - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.MemberRoleName, - Organization: "0", - }, - }, - }, nil - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - Group: "example", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"member","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Bad Place","defaultRole":"member"}],"currentOrganization":{"id":"0","name":"The Bad Place","defaultRole":"member"}}`, - }, - { - name: "Existing user - Chronograf is not private, user has SuperAdmin status, user is in auth0 superadmin group", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - }, - fields: fields{ - UseAuth: true, - SuperAdminProviderGroups: superAdminProviderGroups{ - auth0: "example", - }, - Logger: &chronograf.NoopLogger{}, - ConfigStore: mocks.ConfigStore{ - Config: &chronograf.Config{}, - }, - MappingsStore: &mocks.MappingsStore{ - AllF: func(ctx context.Context) ([]chronograf.Mapping, error) { - return []chronograf.Mapping{ - { - Organization: "0", - Provider: chronograf.MappingWildcard, - Scheme: chronograf.MappingWildcard, - ProviderOrganization: chronograf.MappingWildcard, - }, - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "The Bad Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - }, - UsersStore: &mocks.UsersStore{ - NumF: func(ctx context.Context) (int, error) { - // This function gets to verify that there is at least one first user - return 1, nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - Name: "secret", - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.MemberRoleName, - Organization: "0", - }, - }, - SuperAdmin: true, - }, nil - }, - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return u, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "secret", - Issuer: "auth0", - Group: "example", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"secret","roles":[{"name":"member","organization":"0"}],"provider":"auth0","scheme":"oauth2","superAdmin":true,"links":{"self":"/chronograf/v1/organizations/0/users/0"},"organizations":[{"id":"0","name":"The Bad Place","defaultRole":"member"}],"currentOrganization":{"id":"0","name":"The Bad Place","defaultRole":"member"}}`, - }, - } - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue(context.Background(), oauth2.PrincipalKey, tt.principal)) - s := &Service{ - Store: &mocks.Store{ - UsersStore: tt.fields.UsersStore, - OrganizationsStore: tt.fields.OrganizationsStore, - MappingsStore: tt.fields.MappingsStore, - ConfigStore: tt.fields.ConfigStore, - }, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - SuperAdminProviderGroups: tt.fields.SuperAdminProviderGroups, - } - - s.Me(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. Me() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. Me() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody == "" { - continue - } - if eq, err := jsonEqual(tt.wantBody, string(body)); err != nil || !eq { - t.Errorf("%q. Me() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} - -func TestService_UpdateMe(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - OrganizationsStore chronograf.OrganizationsStore - Logger chronograf.Logger - UseAuth bool - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - meRequest *meRequest - auth mocks.Authenticator - } - tests := []struct { - name string - fields fields - args args - principal oauth2.Principal - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Set the current User's organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - meRequest: &meRequest{ - Organization: "1337", - }, - auth: mocks.Authenticator{}, - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - Name: "me", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.AdminRoleName, - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - switch *q.ID { - case "0": - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.AdminRoleName, - }, nil - case "1337": - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - } - return nil, nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "me", - Issuer: "github", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/1337/users/0"},"organizations":[{"id":"1337","name":"The ShillBillThrilliettas"}],"currentOrganization":{"id":"1337","name":"The ShillBillThrilliettas"}}`, - }, - { - name: "Change the current User's organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - meRequest: &meRequest{ - Organization: "1337", - }, - auth: mocks.Authenticator{}, - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - Name: "me", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.EditorRoleName, - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - switch *q.ID { - case "1337": - return &chronograf.Organization{ - ID: "1337", - Name: "The ThrillShilliettos", - }, nil - case "0": - return &chronograf.Organization{ - ID: "0", - Name: "Default", - DefaultRole: roles.EditorRoleName, - }, nil - } - return nil, nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "me", - Issuer: "github", - Organization: "1338", - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"name":"me","roles":[{"name":"admin","organization":"1337"}],"provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/organizations/1337/users/0"},"organizations":[{"id":"1337","name":"The ThrillShilliettos"}],"currentOrganization":{"id":"1337","name":"The ThrillShilliettos"}}`, - }, - { - name: "Unable to find requested user in valid organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - meRequest: &meRequest{ - Organization: "1337", - }, - auth: mocks.Authenticator{}, - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - Name: "me", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1338", - }, - }, - }, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - if q.ID == nil { - return nil, fmt.Errorf("invalid organization query: missing ID") - } - return &chronograf.Organization{ - ID: "1337", - Name: "The ShillBillThrilliettas", - }, nil - }, - }, - }, - principal: oauth2.Principal{ - Subject: "me", - Issuer: "github", - Organization: "1338", - }, - wantStatus: http.StatusForbidden, - wantContentType: "application/json", - wantBody: `{"code":403,"message":"user not found"}`, - }, - { - name: "Unable to find requested organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest("GET", "http://example.com/foo", nil), - meRequest: &meRequest{ - Organization: "1337", - }, - auth: mocks.Authenticator{}, - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - if q.Name == nil || q.Provider == nil || q.Scheme == nil { - return nil, fmt.Errorf("invalid user query: missing Name, Provider, and/or Scheme") - } - return &chronograf.User{ - Name: "me", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1337", - }, - }, - }, nil - }, - UpdateF: func(ctx context.Context, u *chronograf.User) error { - return nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "0", - }, nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return nil, chronograf.ErrOrganizationNotFound - }, - }, - }, - principal: oauth2.Principal{ - Subject: "me", - Issuer: "github", - Organization: "1338", - }, - wantStatus: http.StatusBadRequest, - wantContentType: "application/json", - wantBody: `{"code":400,"message":"organization not found"}`, - }, - } - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue(context.Background(), oauth2.PrincipalKey, tt.principal)) - s := &Service{ - Store: &Store{ - UsersStore: tt.fields.UsersStore, - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - } - - buf, _ := json.Marshal(tt.args.meRequest) - tt.args.r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - tt.args.auth.Principal = tt.principal - - s.UpdateMe(&tt.args.auth)(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. UpdateMe() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. UpdateMe() = %v, want %v", tt.name, content, tt.wantContentType) - } - if eq, err := jsonEqual(tt.wantBody, string(body)); err != nil || !eq { - t.Errorf("%q. UpdateMe() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} diff --git a/chronograf/server/middle.go b/chronograf/server/middle.go deleted file mode 100644 index b6ba8afc2e1..00000000000 --- a/chronograf/server/middle.go +++ /dev/null @@ -1,57 +0,0 @@ -package server - -import ( - "net/http" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" -) - -// RouteMatchesPrincipal checks that the organization on context matches the organization -// in the route. -func RouteMatchesPrincipal( - store DataStore, - useAuth bool, - logger chronograf.Logger, - next http.HandlerFunc, -) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - if !useAuth { - next(w, r) - return - } - - log := logger. - WithField("component", "org_match"). - WithField("remote_addr", r.RemoteAddr). - WithField("method", r.Method). - WithField("url", r.URL) - - orgID := httprouter.GetParamFromContext(ctx, "oid") - p, err := getValidPrincipal(ctx) - if err != nil { - log.Error("Failed to retrieve principal from context") - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - - if p.Organization == "" { - defaultOrg, err := store.Organizations(ctx).DefaultOrganization(ctx) - if err != nil { - log.Error("Failed to look up default organization") - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - p.Organization = defaultOrg.ID - } - - if orgID != p.Organization { - log.Error("Route organization does not match the organization on principal") - Error(w, http.StatusForbidden, "User is not authorized", logger) - return - } - - next(w, r) - } -} diff --git a/chronograf/server/middle_test.go b/chronograf/server/middle_test.go deleted file mode 100644 index 12dbb007129..00000000000 --- a/chronograf/server/middle_test.go +++ /dev/null @@ -1,195 +0,0 @@ -package server - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -func TestRouteMatchesPrincipal(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - Logger chronograf.Logger - } - type args struct { - useAuth bool - principal *oauth2.Principal - routerParams *httprouter.Params - } - type wants struct { - matches bool - } - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "route matches request params", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "default", - }, nil - }, - }, - }, - args: args{ - useAuth: true, - principal: &oauth2.Principal{ - Subject: "user", - Issuer: "github", - Organization: "default", - }, - routerParams: &httprouter.Params{ - { - Key: "oid", - Value: "default", - }, - }, - }, - wants: wants{ - matches: true, - }, - }, - { - name: "route does not match request params", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "default", - }, nil - }, - }, - }, - args: args{ - useAuth: true, - principal: &oauth2.Principal{ - Subject: "user", - Issuer: "github", - Organization: "default", - }, - routerParams: &httprouter.Params{ - { - Key: "oid", - Value: "other", - }, - }, - }, - wants: wants{ - matches: false, - }, - }, - { - name: "missing principal", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "default", - }, nil - }, - }, - }, - args: args{ - useAuth: true, - principal: nil, - routerParams: &httprouter.Params{ - { - Key: "oid", - Value: "other", - }, - }, - }, - wants: wants{ - matches: false, - }, - }, - { - name: "not using auth", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - DefaultOrganizationF: func(ctx context.Context) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "default", - }, nil - }, - }, - }, - args: args{ - useAuth: false, - principal: &oauth2.Principal{ - Subject: "user", - Issuer: "github", - Organization: "default", - }, - routerParams: &httprouter.Params{ - { - Key: "oid", - Value: "other", - }, - }, - }, - wants: wants{ - matches: true, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - store := &mocks.Store{ - OrganizationsStore: tt.fields.OrganizationsStore, - } - var matches bool - next := func(w http.ResponseWriter, r *http.Request) { - matches = true - } - fn := RouteMatchesPrincipal( - store, - tt.args.useAuth, - tt.fields.Logger, - next, - ) - - w := httptest.NewRecorder() - url := "http://any.url" - r := httptest.NewRequest( - "GET", - url, - nil, - ) - if tt.args.routerParams != nil { - r = r.WithContext(httprouter.WithParams(r.Context(), *tt.args.routerParams)) - } - if tt.args.principal == nil { - r = r.WithContext(context.WithValue(r.Context(), oauth2.PrincipalKey, nil)) - } else { - r = r.WithContext(context.WithValue(r.Context(), oauth2.PrincipalKey, *tt.args.principal)) - } - fn(w, r) - - if matches != tt.wants.matches { - t.Errorf("%q. RouteMatchesPrincipal() = %v, expected %v", tt.name, matches, tt.wants.matches) - } - - if !matches && w.Code != http.StatusForbidden { - t.Errorf("%q. RouteMatchesPrincipal() Status Code = %v, expected %v", tt.name, w.Code, http.StatusForbidden) - } - - }) - } -} diff --git a/chronograf/server/mountable_router.go b/chronograf/server/mountable_router.go deleted file mode 100644 index 1ae275cfdf6..00000000000 --- a/chronograf/server/mountable_router.go +++ /dev/null @@ -1,59 +0,0 @@ -package server - -import ( - "net/http" - libpath "path" - - "github.com/influxdata/influxdb/v2/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(libpath.Join(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(libpath.Join(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(libpath.Join(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(libpath.Join(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(libpath.Join(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, libpath.Join(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) -} diff --git a/chronograf/server/mountable_router_test.go b/chronograf/server/mountable_router_test.go deleted file mode 100644 index 2eec8a0593a..00000000000 --- a/chronograf/server/mountable_router_test.go +++ /dev/null @@ -1,240 +0,0 @@ -package server_test - -import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/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.Fprint(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) - } -} diff --git a/chronograf/server/mux.go b/chronograf/server/mux.go deleted file mode 100644 index 7671291bc7a..00000000000 --- a/chronograf/server/mux.go +++ /dev/null @@ -1,399 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - "path" - "strconv" - "strings" - - "github.com/NYTimes/gziphandler" - "github.com/bouk/httprouter" - jhttprouter "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -const ( - // JSONType the mimetype for a json request - JSONType = "application/json" -) - -// MuxOpts are the options for the router. Mostly related to auth. -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 - UseAuth bool // UseAuth turns on Github OAuth and JWT - Auth oauth2.Authenticator // Auth is used to authenticate and authorize - ProviderFuncs []func(func(oauth2.Provider, oauth2.Mux)) - StatusFeedURL string // JSON Feed URL for the client Status page News Feed - CustomLinks map[string]string // Any custom external links for client's User menu -} - -// NewMux attaches all the route handlers; handler returned servers chronograf. -func NewMux(opts MuxOpts, service Service) http.Handler { - hr := httprouter.New() - - /* React Application */ - assets := Assets(AssetsOpts{ - Develop: opts.Develop, - Logger: opts.Logger, - }) - - // Prefix any URLs found in the React assets with any configured basepath - prefixedAssets := NewDefaultURLPrefixer(opts.Basepath, assets, opts.Logger) - - // Compress the assets with gzip if an accepted encoding - 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. - hr.NotFound = compressed - - var router chronograf.Router = hr - - // Set route prefix for all routes if basepath is present - if opts.Basepath != "" { - 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) - } - - EnsureMember := func(next http.HandlerFunc) http.HandlerFunc { - return AuthorizedUser( - service.Store, - opts.UseAuth, - roles.MemberRoleName, - opts.Logger, - next, - ) - } - _ = EnsureMember - EnsureViewer := func(next http.HandlerFunc) http.HandlerFunc { - return AuthorizedUser( - service.Store, - opts.UseAuth, - roles.ViewerRoleName, - opts.Logger, - next, - ) - } - EnsureEditor := func(next http.HandlerFunc) http.HandlerFunc { - return AuthorizedUser( - service.Store, - opts.UseAuth, - roles.EditorRoleName, - opts.Logger, - next, - ) - } - EnsureAdmin := func(next http.HandlerFunc) http.HandlerFunc { - return AuthorizedUser( - service.Store, - opts.UseAuth, - roles.AdminRoleName, - opts.Logger, - next, - ) - } - EnsureSuperAdmin := func(next http.HandlerFunc) http.HandlerFunc { - return AuthorizedUser( - service.Store, - opts.UseAuth, - roles.SuperAdminStatus, - opts.Logger, - next, - ) - } - - rawStoreAccess := func(next http.HandlerFunc) http.HandlerFunc { - return RawStoreAccess(opts.Logger, next) - } - - ensureOrgMatches := func(next http.HandlerFunc) http.HandlerFunc { - return RouteMatchesPrincipal( - service.Store, - opts.UseAuth, - opts.Logger, - next, - ) - } - - /* Documentation */ - router.GET("/swagger.json", Spec()) - router.GET("/docs", Redoc("/swagger.json")) - - /* API */ - // Organizations - router.GET("/chronograf/v1/organizations", EnsureAdmin(service.Organizations)) - router.POST("/chronograf/v1/organizations", EnsureSuperAdmin(service.NewOrganization)) - - router.GET("/chronograf/v1/organizations/:oid", EnsureAdmin(service.OrganizationID)) - router.PATCH("/chronograf/v1/organizations/:oid", EnsureSuperAdmin(service.UpdateOrganization)) - router.DELETE("/chronograf/v1/organizations/:oid", EnsureSuperAdmin(service.RemoveOrganization)) - - // Mappings - router.GET("/chronograf/v1/mappings", EnsureSuperAdmin(service.Mappings)) - router.POST("/chronograf/v1/mappings", EnsureSuperAdmin(service.NewMapping)) - - router.PUT("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.UpdateMapping)) - router.DELETE("/chronograf/v1/mappings/:id", EnsureSuperAdmin(service.RemoveMapping)) - - // Source Proxy to Influx; Has gzip compression around the handler - influx := gziphandler.GzipHandler(http.HandlerFunc(EnsureViewer(service.Influx))) - router.Handler("POST", "/chronograf/v1/sources/:id/proxy", influx) - - // Write proxies line protocol write requests to InfluxDB - router.POST("/chronograf/v1/sources/:id/write", EnsureViewer(service.Write)) - - // Queries is used to analyze a specific queries and does not create any - // resources. It's a POST because Queries are POSTed to InfluxDB, but this - // only modifies InfluxDB resources with certain metaqueries, e.g. DROP DATABASE. - // - // Admins should ensure that the InfluxDB source as the proper permissions - // intended for Chronograf Users with the Viewer Role type. - router.POST("/chronograf/v1/sources/:id/queries", EnsureViewer(service.Queries)) - - // Annotations are user-defined events associated with this source - router.GET("/chronograf/v1/sources/:id/annotations", EnsureViewer(service.Annotations)) - router.POST("/chronograf/v1/sources/:id/annotations", EnsureEditor(service.NewAnnotation)) - router.GET("/chronograf/v1/sources/:id/annotations/:aid", EnsureViewer(service.Annotation)) - router.DELETE("/chronograf/v1/sources/:id/annotations/:aid", EnsureEditor(service.RemoveAnnotation)) - router.PATCH("/chronograf/v1/sources/:id/annotations/:aid", EnsureEditor(service.UpdateAnnotation)) - - // All possible permissions for users in this source - router.GET("/chronograf/v1/sources/:id/permissions", EnsureViewer(service.Permissions)) - - // Services are resources that chronograf proxies to - router.GET("/chronograf/v1/sources/:id/services", EnsureViewer(service.Services)) - router.POST("/chronograf/v1/sources/:id/services", EnsureEditor(service.NewService)) - router.GET("/chronograf/v1/sources/:id/services/:kid", EnsureViewer(service.ServiceID)) - router.PATCH("/chronograf/v1/sources/:id/services/:kid", EnsureEditor(service.UpdateService)) - router.DELETE("/chronograf/v1/sources/:id/services/:kid", EnsureEditor(service.RemoveService)) - - // Service Proxy - router.GET("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureViewer(service.ProxyGet)) - router.POST("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyPost)) - router.PATCH("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyPatch)) - router.DELETE("/chronograf/v1/sources/:id/services/:kid/proxy", EnsureEditor(service.ProxyDelete)) - - // Layouts - router.GET("/chronograf/v1/layouts", EnsureViewer(service.Layouts)) - router.GET("/chronograf/v1/layouts/:id", EnsureViewer(service.LayoutsID)) - - // Users associated with Chronograf - router.GET("/chronograf/v1/me", service.Me) - - // Set current chronograf organization the user is logged into - router.PUT("/chronograf/v1/me", service.UpdateMe(opts.Auth)) - - // TODO(desa): what to do about admin's being able to set superadmin - router.GET("/chronograf/v1/organizations/:oid/users", EnsureAdmin(ensureOrgMatches(service.Users))) - router.POST("/chronograf/v1/organizations/:oid/users", EnsureAdmin(ensureOrgMatches(service.NewUser))) - - router.GET("/chronograf/v1/organizations/:oid/users/:id", EnsureAdmin(ensureOrgMatches(service.UserID))) - router.DELETE("/chronograf/v1/organizations/:oid/users/:id", EnsureAdmin(ensureOrgMatches(service.RemoveUser))) - router.PATCH("/chronograf/v1/organizations/:oid/users/:id", EnsureAdmin(ensureOrgMatches(service.UpdateUser))) - - router.GET("/chronograf/v1/users", EnsureSuperAdmin(rawStoreAccess(service.Users))) - router.POST("/chronograf/v1/users", EnsureSuperAdmin(rawStoreAccess(service.NewUser))) - - router.GET("/chronograf/v1/users/:id", EnsureSuperAdmin(rawStoreAccess(service.UserID))) - router.DELETE("/chronograf/v1/users/:id", EnsureSuperAdmin(rawStoreAccess(service.RemoveUser))) - router.PATCH("/chronograf/v1/users/:id", EnsureSuperAdmin(rawStoreAccess(service.UpdateUser))) - - // Dashboards - router.GET("/chronograf/v1/dashboards", EnsureViewer(service.Dashboards)) - router.POST("/chronograf/v1/dashboards", EnsureEditor(service.NewDashboard)) - - router.GET("/chronograf/v1/dashboards/:id", EnsureViewer(service.DashboardID)) - router.DELETE("/chronograf/v1/dashboards/:id", EnsureEditor(service.RemoveDashboard)) - router.PUT("/chronograf/v1/dashboards/:id", EnsureEditor(service.ReplaceDashboard)) - router.PATCH("/chronograf/v1/dashboards/:id", EnsureEditor(service.UpdateDashboard)) - // Dashboard Cells - router.GET("/chronograf/v1/dashboards/:id/cells", EnsureViewer(service.DashboardCells)) - router.POST("/chronograf/v1/dashboards/:id/cells", EnsureEditor(service.NewDashboardCell)) - - router.GET("/chronograf/v1/dashboards/:id/cells/:cid", EnsureViewer(service.DashboardCellID)) - router.DELETE("/chronograf/v1/dashboards/:id/cells/:cid", EnsureEditor(service.RemoveDashboardCell)) - router.PUT("/chronograf/v1/dashboards/:id/cells/:cid", EnsureEditor(service.ReplaceDashboardCell)) - // Dashboard Templates - router.GET("/chronograf/v1/dashboards/:id/templates", EnsureViewer(service.Templates)) - router.POST("/chronograf/v1/dashboards/:id/templates", EnsureEditor(service.NewTemplate)) - - router.GET("/chronograf/v1/dashboards/:id/templates/:tid", EnsureViewer(service.TemplateID)) - router.DELETE("/chronograf/v1/dashboards/:id/templates/:tid", EnsureEditor(service.RemoveTemplate)) - router.PUT("/chronograf/v1/dashboards/:id/templates/:tid", EnsureEditor(service.ReplaceTemplate)) - - // Databases - router.GET("/chronograf/v1/sources/:id/dbs", EnsureViewer(service.GetDatabases)) - router.POST("/chronograf/v1/sources/:id/dbs", EnsureEditor(service.NewDatabase)) - - router.DELETE("/chronograf/v1/sources/:id/dbs/:db", EnsureEditor(service.DropDatabase)) - - // Retention Policies - router.GET("/chronograf/v1/sources/:id/dbs/:db/rps", EnsureViewer(service.RetentionPolicies)) - router.POST("/chronograf/v1/sources/:id/dbs/:db/rps", EnsureEditor(service.NewRetentionPolicy)) - - router.PUT("/chronograf/v1/sources/:id/dbs/:db/rps/:rp", EnsureEditor(service.UpdateRetentionPolicy)) - router.DELETE("/chronograf/v1/sources/:id/dbs/:db/rps/:rp", EnsureEditor(service.DropRetentionPolicy)) - - // Measurements - router.GET("/chronograf/v1/sources/:id/dbs/:db/measurements", EnsureViewer(service.Measurements)) - - // Global application config for Chronograf - router.GET("/chronograf/v1/config", EnsureSuperAdmin(service.Config)) - router.GET("/chronograf/v1/config/auth", EnsureSuperAdmin(service.AuthConfig)) - router.PUT("/chronograf/v1/config/auth", EnsureSuperAdmin(service.ReplaceAuthConfig)) - - // Organization config settings for Chronograf - router.GET("/chronograf/v1/org_config", EnsureViewer(service.OrganizationConfig)) - router.GET("/chronograf/v1/org_config/logviewer", EnsureViewer(service.OrganizationLogViewerConfig)) - router.PUT("/chronograf/v1/org_config/logviewer", EnsureEditor(service.ReplaceOrganizationLogViewerConfig)) - - router.GET("/chronograf/v1/env", EnsureViewer(service.Environment)) - - allRoutes := &AllRoutes{ - Logger: opts.Logger, - StatusFeed: opts.StatusFeedURL, - CustomLinks: opts.CustomLinks, - } - - getPrincipal := func(r *http.Request) oauth2.Principal { - p, _ := HasAuthorizedToken(opts.Auth, r) - return p - } - allRoutes.GetPrincipal = getPrincipal - router.Handler("GET", "/chronograf/v1/", allRoutes) - - var out http.Handler - - /* Authentication */ - if opts.UseAuth { - // Encapsulate the router with OAuth2 - var auth http.Handler - auth, allRoutes.AuthRoutes = AuthAPI(opts, router) - allRoutes.LogoutLink = path.Join(opts.Basepath, "/oauth/logout") - - // Create middleware that redirects to the appropriate provider logout - router.GET("/oauth/logout", Logout("/", opts.Basepath, allRoutes.AuthRoutes)) - out = Logger(opts.Logger, FlushingHandler(auth)) - } else { - out = Logger(opts.Logger, FlushingHandler(router)) - } - - return out -} - -// AuthAPI adds the OAuth routes if auth is enabled. -func AuthAPI(opts MuxOpts, router chronograf.Router) (http.Handler, AuthRoutes) { - routes := AuthRoutes{} - for _, pf := range opts.ProviderFuncs { - pf(func(p oauth2.Provider, m oauth2.Mux) { - urlName := PathEscape(strings.ToLower(p.Name())) - - loginPath := path.Join("/oauth", urlName, "login") - logoutPath := path.Join("/oauth", urlName, "logout") - callbackPath := path.Join("/oauth", urlName, "callback") - - router.Handler("GET", loginPath, m.Login()) - router.Handler("GET", logoutPath, m.Logout()) - router.Handler("GET", callbackPath, m.Callback()) - routes = append(routes, AuthRoute{ - Name: p.Name(), - Label: strings.Title(p.Name()), - // AuthRoutes are content served to the page. When Basepath is set, it - // says that all content served to the page will be prefixed with the - // basepath. Since these routes are consumed by JS, it will need the - // basepath set to traverse a proxy correctly - Login: path.Join(opts.Basepath, loginPath), - Logout: path.Join(opts.Basepath, logoutPath), - Callback: path.Join(opts.Basepath, callbackPath), - }) - }) - } - - rootPath := path.Join(opts.Basepath, "/chronograf/v1") - logoutPath := path.Join(opts.Basepath, "/oauth/logout") - - tokenMiddleware := AuthorizedToken(opts.Auth, opts.Logger, router) - // Wrap the API with token validation middleware. - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - cleanPath := path.Clean(r.URL.Path) // compare ignoring path garbage, trailing slashes, etc. - if (strings.HasPrefix(cleanPath, rootPath) && len(cleanPath) > len(rootPath)) || cleanPath == logoutPath { - tokenMiddleware.ServeHTTP(w, r) - return - } - router.ServeHTTP(w, r) - }), routes -} - -func encodeJSON(w http.ResponseWriter, status int, v interface{}, logger chronograf.Logger) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - if err := json.NewEncoder(w).Encode(v); err != nil { - unknownErrorWithMessage(w, err, logger) - } -} - -// Error writes an JSON message -func Error(w http.ResponseWriter, code int, msg string, logger chronograf.Logger) { - e := ErrorMessage{ - Code: code, - Message: msg, - } - b, err := json.Marshal(e) - if err != nil { - code = http.StatusInternalServerError - b = []byte(`{"code": 500, "message":"server_error"}`) - } - - logger. - WithField("component", "server"). - WithField("http_status ", code). - Error("Error message ", msg) - w.Header().Set("Content-Type", JSONType) - w.WriteHeader(code) - _, _ = w.Write(b) -} - -func invalidData(w http.ResponseWriter, err error, logger chronograf.Logger) { - Error(w, http.StatusUnprocessableEntity, fmt.Sprintf("%v", err), logger) -} - -func invalidJSON(w http.ResponseWriter, logger chronograf.Logger) { - Error(w, http.StatusBadRequest, "unparsable JSON", logger) -} - -func unknownErrorWithMessage(w http.ResponseWriter, err error, logger chronograf.Logger) { - Error(w, http.StatusInternalServerError, fmt.Sprintf("unknown error: %v", err), logger) -} - -func notFound(w http.ResponseWriter, id interface{}, logger chronograf.Logger) { - Error(w, http.StatusNotFound, fmt.Sprintf("ID %v not found", id), logger) -} - -func paramID(key string, r *http.Request) (int, error) { - ctx := r.Context() - param := jhttprouter.ParamsFromContext(ctx).ByName(key) - id, err := strconv.Atoi(param) - if err != nil { - return -1, fmt.Errorf("error converting ID %s", param) - } - return id, nil -} - -func paramStr(key string, r *http.Request) (string, error) { - ctx := r.Context() - param := jhttprouter.ParamsFromContext(ctx).ByName(key) - return param, nil -} diff --git a/chronograf/server/org_config.go b/chronograf/server/org_config.go deleted file mode 100644 index 1ea43943902..00000000000 --- a/chronograf/server/org_config.go +++ /dev/null @@ -1,180 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -type organizationConfigLinks struct { - Self string `json:"self"` // Self link mapping to this resource - LogViewer string `json:"logViewer"` // LogViewer link to the organization log viewer config endpoint -} - -type organizationConfigResponse struct { - Links organizationConfigLinks `json:"links"` - chronograf.OrganizationConfig -} - -func newOrganizationConfigResponse(c chronograf.OrganizationConfig) *organizationConfigResponse { - return &organizationConfigResponse{ - Links: organizationConfigLinks{ - Self: "/chronograf/v1/org_config", - LogViewer: "/chronograf/v1/org_config/logviewer", - }, - OrganizationConfig: c, - } -} - -type logViewerConfigResponse struct { - Links selfLinks `json:"links"` - chronograf.LogViewerConfig -} - -func newLogViewerConfigResponse(c chronograf.LogViewerConfig) *logViewerConfigResponse { - return &logViewerConfigResponse{ - Links: selfLinks{ - Self: "/chronograf/v1/org_config/logviewer", - }, - LogViewerConfig: c, - } -} - -// OrganizationConfig retrieves the organization-wide config settings -func (s *Service) OrganizationConfig(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - orgID, ok := hasOrganizationContext(ctx) - if !ok { - Error(w, http.StatusBadRequest, "Organization not found on context", s.Logger) - return - } - - config, err := s.Store.OrganizationConfig(ctx).FindOrCreate(ctx, orgID) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newOrganizationConfigResponse(*config) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// OrganizationLogViewerConfig retrieves the log viewer UI section of the organization config -// This uses a FindOrCreate function to ensure that any new organizations have -// default organization config values, without having to associate organization creation with -// organization config creation. -func (s *Service) OrganizationLogViewerConfig(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - orgID, ok := hasOrganizationContext(ctx) - if !ok { - Error(w, http.StatusBadRequest, "Organization not found on context", s.Logger) - return - } - - config, err := s.Store.OrganizationConfig(ctx).FindOrCreate(ctx, orgID) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newLogViewerConfigResponse(config.LogViewer) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// ReplaceOrganizationLogViewerConfig replaces the log viewer UI section of the organization config -func (s *Service) ReplaceOrganizationLogViewerConfig(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - orgID, ok := hasOrganizationContext(ctx) - if !ok { - Error(w, http.StatusBadRequest, "Organization not found on context", s.Logger) - return - } - - var logViewerConfig chronograf.LogViewerConfig - if err := json.NewDecoder(r.Body).Decode(&logViewerConfig); err != nil { - invalidJSON(w, s.Logger) - return - } - if err := validLogViewerConfig(logViewerConfig); err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - config, err := s.Store.OrganizationConfig(ctx).FindOrCreate(ctx, orgID) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - config.LogViewer = logViewerConfig - if err := s.Store.OrganizationConfig(ctx).Put(ctx, config); err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - res := newLogViewerConfigResponse(config.LogViewer) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// validLogViewerConfig ensures that the request body log viewer UI config is valid -// to be valid, it must: not be empty, have at least one column, not have multiple -// columns with the same name or position value, each column must have a visibility -// of either "visible" or "hidden" and if a column is of type severity, it must have -// at least one severity format of type icon, text, or both -func validLogViewerConfig(c chronograf.LogViewerConfig) error { - if len(c.Columns) == 0 { - return fmt.Errorf("invalid log viewer config: must have at least 1 column") - } - - nameMatcher := map[string]bool{} - positionMatcher := map[int32]bool{} - - for _, clm := range c.Columns { - iconCount := 0 - textCount := 0 - visibility := 0 - - // check that each column has a unique value for the name and position properties - if _, ok := nameMatcher[clm.Name]; ok { - return fmt.Errorf("invalid log viewer config: Duplicate column name %s", clm.Name) - } - nameMatcher[clm.Name] = true - if _, ok := positionMatcher[clm.Position]; ok { - return fmt.Errorf("invalid log viewer config: Multiple columns with same position value") - } - positionMatcher[clm.Position] = true - - for _, e := range clm.Encodings { - if e.Type == "visibility" { - visibility++ - if !(e.Value == "visible" || e.Value == "hidden") { - return fmt.Errorf("invalid log viewer config: invalid visibility in column %s", clm.Name) - } - } - - if clm.Name == "severity" { - if e.Value == "icon" { - iconCount++ - } else if e.Value == "text" { - textCount++ - } - } - } - - if visibility != 1 { - return fmt.Errorf("invalid log viewer config: missing visibility encoding in column %s", clm.Name) - } - - if clm.Name == "severity" { - if iconCount+textCount == 0 || iconCount > 1 || textCount > 1 { - return fmt.Errorf("invalid log viewer config: invalid number of severity format encodings in column %s", clm.Name) - } - } - } - - return nil -} diff --git a/chronograf/server/org_config_test.go b/chronograf/server/org_config_test.go deleted file mode 100644 index 3ec842fefd4..00000000000 --- a/chronograf/server/org_config_test.go +++ /dev/null @@ -1,1076 +0,0 @@ -package server - -import ( - "bytes" - "context" - "encoding/json" - "io/ioutil" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/organizations" -) - -func TestOrganizationConfig(t *testing.T) { - type args struct { - organizationID string - } - type fields struct { - organizationConfigStore chronograf.OrganizationConfigStore - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - args args - fields fields - wants wants - }{ - { - name: "Get organization configuration", - args: args{ - organizationID: "default", - }, - fields: fields{ - organizationConfigStore: &mocks.OrganizationConfigStore{ - FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - switch orgID { - case "default": - return &chronograf.OrganizationConfig{ - OrganizationID: "default", - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "time", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - { - Name: "severity", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "icon", - }, - { - Type: "label", - Value: "text", - }, - }, - }, - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "procid", - Position: 5, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Proc ID", - }, - }, - }, - { - Name: "appname", - Position: 6, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "displayName", - Value: "Application", - }, - }, - }, - { - Name: "host", - Position: 7, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, nil - default: - return nil, chronograf.ErrOrganizationConfigNotFound - } - }, - }, - }, - wants: wants{ - statusCode: 200, - contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"organization":"default","logViewer":{"columns":[{"name":"time","position":0,"encodings":[{"type":"visibility","value":"hidden"}]},{"name":"severity","position":1,"encodings":[{"type":"visibility","value":"visible"},{"type":"label","value":"icon"},{"type":"label","value":"text"}]},{"name":"timestamp","position":2,"encodings":[{"type":"visibility","value":"visible"}]},{"name":"message","position":3,"encodings":[{"type":"visibility","value":"visible"}]},{"name":"facility","position":4,"encodings":[{"type":"visibility","value":"visible"}]},{"name":"procid","position":5,"encodings":[{"type":"visibility","value":"visible"},{"type":"displayName","value":"Proc ID"}]},{"name":"appname","position":6,"encodings":[{"type":"visibility","value":"visible"},{"type":"displayName","value":"Application"}]},{"name":"host","position":7,"encodings":[{"type":"visibility","value":"visible"}]}]}}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - OrganizationConfigStore: tt.fields.organizationConfigStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - ctx := context.WithValue(r.Context(), organizations.ContextKey, tt.args.organizationID) - r = r.WithContext(ctx) - - s.OrganizationConfig(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. OrganizationConfig() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. OrganizationConfig() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. OrganizationConfig() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} - -func TestLogViewerOrganizationConfig(t *testing.T) { - type args struct { - organizationID string - } - type fields struct { - organizationConfigStore chronograf.OrganizationConfigStore - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - args args - fields fields - wants wants - }{ - { - name: "Get log viewer configuration", - args: args{ - organizationID: "default", - }, - fields: fields{ - organizationConfigStore: &mocks.OrganizationConfigStore{ - FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - switch orgID { - case "default": - return &chronograf.OrganizationConfig{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "color", - Value: "emergency", - Name: "ruby", - }, - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - { - Type: "displayName", - Value: "Log Severity", - }, - }, - }, - }, - }, - }, nil - default: - return nil, chronograf.ErrOrganizationConfigNotFound - } - }, - }, - }, - wants: wants{ - statusCode: 200, - contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/org_config/logviewer"},"columns":[{"name":"severity","position":0,"encodings":[{"type":"color","value":"emergency","name":"ruby"},{"type":"color","value":"info","name":"rainforest"},{"type":"displayName","value":"Log Severity"}]}]}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - OrganizationConfigStore: tt.fields.organizationConfigStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - ctx := context.WithValue(r.Context(), organizations.ContextKey, tt.args.organizationID) - r = r.WithContext(ctx) - - s.OrganizationLogViewerConfig(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. LogViewerOrganizationConfig() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. LogViewerOrganizationConfig() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. LogViewerOrganizationConfig() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} - -func TestReplaceLogViewerOrganizationConfig(t *testing.T) { - type fields struct { - organizationConfigStore chronograf.OrganizationConfigStore - } - type args struct { - payload interface{} // expects JSON serializable struct - organizationID string - } - type wants struct { - statusCode int - contentType string - body string - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "Set log viewer configuration", - fields: fields{ - organizationConfigStore: &mocks.OrganizationConfigStore{ - FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - switch orgID { - case "1337": - return &chronograf.OrganizationConfig{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "icon", - }, - }, - }, - }, - }, - }, nil - default: - return nil, chronograf.ErrOrganizationConfigNotFound - } - }, - PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error { - return nil - }, - }, - }, - args: args{ - payload: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "color", - Value: "info", - Name: "pineapple", - }, - { - Type: "color", - Value: "emergency", - Name: "ruby", - }, - { - Type: "visibility", - Value: "visible", - }, - { - Type: "label", - Value: "icon", - }, - }, - }, - { - Name: "messages", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "displayName", - Value: "Log Messages", - }, - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - organizationID: "1337", - }, - wants: wants{ - statusCode: 200, - contentType: "application/json", - body: `{"links":{"self":"/chronograf/v1/org_config/logviewer"},"columns":[{"name":"severity","position":1,"encodings":[{"type":"color","value":"info","name":"pineapple"},{"type":"color","value":"emergency","name":"ruby"},{"type":"visibility","value":"visible"},{"type":"label","value":"icon"}]},{"name":"messages","position":0,"encodings":[{"type":"displayName","value":"Log Messages"},{"type":"visibility","value":"visible"}]}]}`, - }, - }, - { - name: "Set invalid log viewer configuration – empty", - fields: fields{ - organizationConfigStore: &mocks.OrganizationConfigStore{ - FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - switch orgID { - case "1337": - return &chronograf.OrganizationConfig{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - { - Type: "label", - Value: "icon", - }, - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, nil - default: - return nil, chronograf.ErrOrganizationConfigNotFound - } - }, - PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error { - return nil - }, - }, - }, - args: args{ - payload: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{}, - }, - organizationID: "1337", - }, - wants: wants{ - statusCode: 400, - contentType: "application/json", - body: `{"code":400,"message":"invalid log viewer config: must have at least 1 column"}`, - }, - }, - { - name: "Set invalid log viewer configuration - duplicate column name", - fields: fields{ - organizationConfigStore: &mocks.OrganizationConfigStore{ - FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - switch orgID { - case "1337": - return &chronograf.OrganizationConfig{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "procid", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - }, - }, - }, nil - default: - return nil, chronograf.ErrOrganizationConfigNotFound - } - }, - PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error { - return nil - }, - }, - }, - args: args{ - payload: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "procid", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - { - Name: "procid", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - }, - }, - organizationID: "1337", - }, - wants: wants{ - statusCode: 400, - contentType: "application/json", - body: `{"code":400,"message":"invalid log viewer config: Duplicate column name procid"}`, - }, - }, - { - name: "Set invalid log viewer configuration - multiple columns with same position value", - fields: fields{ - organizationConfigStore: &mocks.OrganizationConfigStore{ - FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - switch orgID { - case "1337": - return &chronograf.OrganizationConfig{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "procid", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - }, - }, - }, nil - default: - return nil, chronograf.ErrOrganizationConfigNotFound - } - }, - PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error { - return nil - }, - }, - }, - args: args{ - payload: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "procid", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - { - Name: "timestamp", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "hidden", - }, - }, - }, - }, - }, - organizationID: "1337", - }, - wants: wants{ - statusCode: 400, - contentType: "application/json", - body: `{"code":400,"message":"invalid log viewer config: Multiple columns with same position value"}`, - }, - }, - { - name: "Set invalid log viewer configuration – no visibility", - fields: fields{ - organizationConfigStore: &mocks.OrganizationConfigStore{ - FindOrCreateF: func(ctx context.Context, orgID string) (*chronograf.OrganizationConfig, error) { - switch orgID { - case "1337": - return &chronograf.OrganizationConfig{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - { - Type: "label", - Value: "icon", - }, - }, - }, - }, - }, - }, nil - default: - return nil, chronograf.ErrOrganizationConfigNotFound - } - }, - PutF: func(ctx context.Context, target *chronograf.OrganizationConfig) error { - return nil - }, - }, - }, - args: args{ - payload: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 1, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "color", - Value: "info", - Name: "pineapple", - }, - { - Type: "color", - Value: "emergency", - Name: "ruby", - }, - { - Type: "label", - Value: "icon", - }, - }, - }, - }, - }, - organizationID: "1337", - }, - wants: wants{ - statusCode: 400, - contentType: "application/json", - body: `{"code":400,"message":"invalid log viewer config: missing visibility encoding in column severity"}`, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - OrganizationConfigStore: tt.fields.organizationConfigStore, - }, - Logger: &chronograf.NoopLogger{}, - } - - w := httptest.NewRecorder() - r := httptest.NewRequest("GET", "http://any.url", nil) - ctx := context.WithValue(r.Context(), organizations.ContextKey, tt.args.organizationID) - r = r.WithContext(ctx) - buf, _ := json.Marshal(tt.args.payload) - r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - - s.ReplaceOrganizationLogViewerConfig(w, r) - - resp := w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wants.statusCode { - t.Errorf("%q. ReplaceLogViewerOrganizationConfig() = %v, want %v", tt.name, resp.StatusCode, tt.wants.statusCode) - } - if tt.wants.contentType != "" && content != tt.wants.contentType { - t.Errorf("%q. ReplaceLogViewerOrganizationConfig() = %v, want %v", tt.name, content, tt.wants.contentType) - } - if eq, _ := jsonEqual(string(body), tt.wants.body); tt.wants.body != "" && !eq { - t.Errorf("%q. ReplaceLogViewerOrganizationConfig() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wants.body) - } - }) - } -} - -func Test_validLogViewerConfig(t *testing.T) { - type args struct { - LogViewer chronograf.LogViewerConfig - } - - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "cannot have 0 columns", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: nil, - }, - }, - wantErr: true, - }, - { - name: "can have 1 column", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "can have more than 1 column", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "facility", - Position: 4, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "cannot have multiple columns with the same name value", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "timestamp", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "cannot have multiple columns with the same position value", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - { - Name: "message", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "each column must have a visibility encoding value of either 'visible' or 'hidden'", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "timestamp", - Position: 2, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "bob", - }, - }, - }, - { - Name: "message", - Position: 3, - Encodings: []chronograf.ColumnEncoding{ - - { - Type: "visibility", - Value: "visible", - }, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "severity column can have 1 of each icon and text label encoding", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - { - Type: "label", - Value: "icon", - }, - { - Type: "label", - Value: "text", - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "severity column can 1 icon label encoding", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - { - Type: "label", - Value: "icon", - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "severity column can have 1 text label encoding", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - { - Type: "label", - Value: "text", - }, - }, - }, - }, - }, - }, - wantErr: false, - }, - { - name: "severity column cannot have 0 label encodings", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "severity column cannot have more than 1 icon label encoding", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - { - Type: "label", - Value: "icon", - }, - { - Type: "label", - Value: "icon", - }, - }, - }, - }, - }, - }, - wantErr: true, - }, - { - name: "severity column cannot have more than 1 text label encoding", - args: args{ - LogViewer: chronograf.LogViewerConfig{ - Columns: []chronograf.LogViewerColumn{ - { - Name: "severity", - Position: 0, - Encodings: []chronograf.ColumnEncoding{ - { - Type: "visibility", - Value: "visible", - }, - { - Type: "color", - Value: "info", - Name: "rainforest", - }, - { - Type: "label", - Value: "text", - }, - { - Type: "label", - Value: "text", - }, - }, - }, - }, - }, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := validLogViewerConfig(tt.args.LogViewer) - - if (tt.wantErr && got == nil) || (!tt.wantErr && got != nil) { - t.Errorf("%q. validLogViewerConfig().\ngot: %v\nwantErr: %v", tt.name, got, tt.wantErr) - } - }) - } -} diff --git a/chronograf/server/organizations.go b/chronograf/server/organizations.go deleted file mode 100644 index 01f2cb8ce97..00000000000 --- a/chronograf/server/organizations.go +++ /dev/null @@ -1,232 +0,0 @@ -package server - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/organizations" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -type organizationRequest struct { - Name string `json:"name"` - DefaultRole string `json:"defaultRole"` -} - -func (r *organizationRequest) ValidCreate() error { - if r.Name == "" { - return fmt.Errorf("name required on Chronograf Organization request body") - } - - return r.ValidDefaultRole() -} - -func (r *organizationRequest) ValidUpdate() error { - if r.Name == "" && r.DefaultRole == "" { - return fmt.Errorf("no fields to update") - } - - if r.DefaultRole != "" { - return r.ValidDefaultRole() - } - - return nil -} - -func (r *organizationRequest) ValidDefaultRole() error { - if r.DefaultRole == "" { - r.DefaultRole = roles.MemberRoleName - } - - switch r.DefaultRole { - case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: - return nil - default: - return fmt.Errorf("default role must be member, viewer, editor, or admin") - } -} - -type organizationResponse struct { - Links selfLinks `json:"links"` - chronograf.Organization -} - -func newOrganizationResponse(o *chronograf.Organization) *organizationResponse { - if o == nil { - o = &chronograf.Organization{} - } - return &organizationResponse{ - Organization: *o, - Links: selfLinks{ - Self: fmt.Sprintf("/chronograf/v1/organizations/%s", o.ID), - }, - } -} - -type organizationsResponse struct { - Links selfLinks `json:"links"` - Organizations []*organizationResponse `json:"organizations"` -} - -func newOrganizationsResponse(orgs []chronograf.Organization) *organizationsResponse { - orgsResp := make([]*organizationResponse, len(orgs)) - for i, org := range orgs { - orgsResp[i] = newOrganizationResponse(&org) - } - return &organizationsResponse{ - Organizations: orgsResp, - Links: selfLinks{ - Self: "/chronograf/v1/organizations", - }, - } -} - -// Organizations retrieves all organizations from store -func (s *Service) Organizations(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - orgs, err := s.Store.Organizations(ctx).All(ctx) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newOrganizationsResponse(orgs) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// NewOrganization adds a new organization to store -func (s *Service) NewOrganization(w http.ResponseWriter, r *http.Request) { - var req organizationRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := req.ValidCreate(); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - org := &chronograf.Organization{ - Name: req.Name, - DefaultRole: req.DefaultRole, - } - - res, err := s.Store.Organizations(ctx).Add(ctx, org) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - // Now that the organization was created, add the user - // making the request to the organization - user, ok := hasUserContext(ctx) - if !ok { - // Best attempt at cleanup the organization if there were any errors - _ = s.Store.Organizations(ctx).Delete(ctx, res) - Error(w, http.StatusInternalServerError, "failed to retrieve user from context", s.Logger) - return - } - - user.Roles = []chronograf.Role{ - { - Organization: res.ID, - Name: roles.AdminRoleName, - }, - } - - orgCtx := context.WithValue(ctx, organizations.ContextKey, res.ID) - _, err = s.Store.Users(orgCtx).Add(orgCtx, user) - if err != nil { - // Best attempt at cleanup the organization if there were any errors adding user to org - _ = s.Store.Organizations(ctx).Delete(ctx, res) - s.Logger.Error("failed to add user to organization", err.Error()) - Error(w, http.StatusInternalServerError, "failed to add user to organization", s.Logger) - return - } - - co := newOrganizationResponse(res) - location(w, co.Links.Self) - encodeJSON(w, http.StatusCreated, co, s.Logger) -} - -// OrganizationID retrieves a organization with ID from store -func (s *Service) OrganizationID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - id := httprouter.GetParamFromContext(ctx, "oid") - - org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &id}) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newOrganizationResponse(org) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// UpdateOrganization updates an organization in the organizations store -func (s *Service) UpdateOrganization(w http.ResponseWriter, r *http.Request) { - var req organizationRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := req.ValidUpdate(); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - id := httprouter.GetParamFromContext(ctx, "oid") - - org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &id}) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - if req.Name != "" { - org.Name = req.Name - } - - if req.DefaultRole != "" { - org.DefaultRole = req.DefaultRole - } - - err = s.Store.Organizations(ctx).Update(ctx, org) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - res := newOrganizationResponse(org) - location(w, res.Links.Self) - encodeJSON(w, http.StatusOK, res, s.Logger) - -} - -// RemoveOrganization removes an organization in the organizations store -func (s *Service) RemoveOrganization(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id := httprouter.GetParamFromContext(ctx, "oid") - - org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &id}) - if err != nil { - Error(w, http.StatusNotFound, err.Error(), s.Logger) - return - } - if err := s.Store.Organizations(ctx).Delete(ctx, org); err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - w.WriteHeader(http.StatusNoContent) -} diff --git a/chronograf/server/organizations_test.go b/chronograf/server/organizations_test.go deleted file mode 100644 index 7392bab6d69..00000000000 --- a/chronograf/server/organizations_test.go +++ /dev/null @@ -1,726 +0,0 @@ -package server - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -func TestService_OrganizationID(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - id string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Get Single Organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1337": - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - }, nil - default: - return nil, fmt.Errorf("organization with ID %s not found", *q.ID) - } - }, - }, - }, - id: "1337", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place"}`, - }, - { - name: "Get Single Organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1337": - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - }, nil - default: - return nil, fmt.Errorf("organization with ID %s not found", *q.ID) - } - }, - }, - }, - id: "1337", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"id":"1337","name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}}`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(httprouter.WithParams( - context.Background(), - httprouter.Params{ - { - Key: "oid", - Value: tt.id, - }, - })) - - s.OrganizationID(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. OrganizationID() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. OrganizationID() = %v, want %v", tt.name, content, tt.wantContentType) - } - if eq, _ := jsonEqual(string(body), tt.wantBody); tt.wantBody != "" && !eq { - t.Errorf("%q. OrganizationID() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - }) - } -} - -func TestService_Organizations(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Get Organizations", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - AllF: func(ctx context.Context) ([]chronograf.Organization, error) { - return []chronograf.Organization{ - chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - }, - chronograf.Organization{ - ID: "100", - Name: "The Bad Place", - }, - }, nil - }, - }, - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/organizations"},"organizations":[{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place"},{"links":{"self":"/chronograf/v1/organizations/100"},"id":"100","name":"The Bad Place"}]}`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: tt.fields.Logger, - } - - s.Organizations(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. Organizations() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. Organizations() = %v, want %v", tt.name, content, tt.wantContentType) - } - if eq, _ := jsonEqual(string(body), tt.wantBody); tt.wantBody != "" && !eq { - t.Errorf("%q. Organizations() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - }) - } -} - -func TestService_UpdateOrganization(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - org *organizationRequest - } - tests := []struct { - name string - fields fields - args args - id string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Update Organization name", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - Name: "The Bad Place", - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"id":"1337","name":"The Bad Place","defaultRole":"viewer","links":{"self":"/chronograf/v1/organizations/1337"}}`, - }, - { - name: "Update Organization - nothing to update", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{}, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - DefaultRole: roles.ViewerRoleName, - }, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"no fields to update"}`, - }, - { - name: "Update Organization default role", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - DefaultRole: roles.ViewerRoleName, - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - DefaultRole: roles.MemberRoleName, - }, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/organizations/1337"},"id":"1337","name":"The Good Place","defaultRole":"viewer"}`, - }, - { - name: "Update Organization - invalid update", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{}, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return nil, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"no fields to update"}`, - }, - { - name: "Update Organization - invalid role", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - DefaultRole: "sillyrole", - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - UpdateF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return nil, nil - }, - }, - }, - id: "1337", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"default role must be member, viewer, editor, or admin"}`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(httprouter.WithParams(context.Background(), - httprouter.Params{ - { - Key: "oid", - Value: tt.id, - }, - })) - - buf, _ := json.Marshal(tt.args.org) - tt.args.r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - s.UpdateOrganization(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. NewOrganization() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. NewOrganization() = %v, want %v", tt.name, content, tt.wantContentType) - } - if eq, _ := jsonEqual(string(body), tt.wantBody); tt.wantBody != "" && !eq { - t.Errorf("%q. NewOrganization() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - }) - } -} - -func TestService_RemoveOrganization(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - id string - wantStatus int - }{ - { - name: "Update Organization name", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - DeleteF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1337": - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - }, nil - default: - return nil, fmt.Errorf("organization with ID %s not found", *q.ID) - } - }, - }, - }, - id: "1337", - wantStatus: http.StatusNoContent, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(httprouter.WithParams(context.Background(), - httprouter.Params{ - { - Key: "oid", - Value: tt.id, - }, - })) - s.RemoveOrganization(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. NewOrganization() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - }) - } -} - -func TestService_NewOrganization(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - UsersStore chronograf.UsersStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - org *organizationRequest - user *chronograf.User - } - tests := []struct { - name string - fields fields - args args - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Create Organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - user: &chronograf.User{ - ID: 1, - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - }, - org: &organizationRequest{ - Name: "The Good Place", - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1, - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - AddF: func(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - }, nil - }, - }, - }, - wantStatus: http.StatusCreated, - wantContentType: "application/json", - wantBody: `{"id":"1337","name":"The Good Place","links":{"self":"/chronograf/v1/organizations/1337"}}`, - }, - { - name: "Fail to create Organization - no org name", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - user: &chronograf.User{ - ID: 1, - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - }, - org: &organizationRequest{}, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1, - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - AddF: func(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { - return nil, nil - }, - }, - }, - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"name required on Chronograf Organization request body"}`, - }, - { - name: "Create Organization - no user on context", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - Name: "The Good Place", - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1, - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - }, nil - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - AddF: func(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - }, nil - }, - DeleteF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - }, - }, - wantStatus: http.StatusInternalServerError, - wantContentType: "application/json", - wantBody: `{"code":500,"message":"failed to retrieve user from context"}`, - }, - { - name: "Create Organization - failed to add user to organization", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - org: &organizationRequest{ - Name: "The Good Place", - }, - user: &chronograf.User{ - ID: 1, - Name: "bobetta", - Provider: "github", - Scheme: "oauth2", - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, u *chronograf.User) (*chronograf.User, error) { - return nil, fmt.Errorf("failed to add user to org") - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - AddF: func(ctx context.Context, o *chronograf.Organization) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "1337", - Name: "The Good Place", - }, nil - }, - DeleteF: func(ctx context.Context, o *chronograf.Organization) error { - return nil - }, - }, - }, - wantStatus: http.StatusInternalServerError, - wantContentType: "application/json", - wantBody: `{"code":500,"message":"failed to add user to organization"}`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - OrganizationsStore: tt.fields.OrganizationsStore, - UsersStore: tt.fields.UsersStore, - }, - Logger: tt.fields.Logger, - } - - ctx := tt.args.r.Context() - ctx = context.WithValue(ctx, UserContextKey, tt.args.user) - tt.args.r = tt.args.r.WithContext(ctx) - - buf, _ := json.Marshal(tt.args.org) - tt.args.r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - s.NewOrganization(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. NewOrganization() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. NewOrganization() = %v, want %v", tt.name, content, tt.wantContentType) - } - if eq, _ := jsonEqual(string(body), tt.wantBody); tt.wantBody != "" && !eq { - t.Errorf("%q. NewOrganization() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - }) - } -} diff --git a/chronograf/server/path.go b/chronograf/server/path.go deleted file mode 100644 index c1293e3cca7..00000000000 --- a/chronograf/server/path.go +++ /dev/null @@ -1,10 +0,0 @@ -package server - -import "net/url" - -// PathEscape escapes the string so it can be safely placed inside a URL path segment. -// Change to url.PathEscape for go 1.8 -func PathEscape(str string) string { - u := &url.URL{Path: str} - return u.String() -} diff --git a/chronograf/server/permissions.go b/chronograf/server/permissions.go deleted file mode 100644 index ae7123e567e..00000000000 --- a/chronograf/server/permissions.go +++ /dev/null @@ -1,55 +0,0 @@ -package server - -import ( - "fmt" - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -// Permissions returns all possible permissions for this source. -func (s *Service) Permissions(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - src, err := s.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, s.Logger) - return - } - - ts, err := s.TimeSeries(src) - if err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - if err = ts.Connect(ctx, &src); err != nil { - msg := fmt.Sprintf("unable to connect to source %d: %v", srcID, err) - Error(w, http.StatusBadRequest, msg, s.Logger) - return - } - - perms := ts.Permissions(ctx) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - httpAPISrcs := "/chronograf/v1/sources" - res := struct { - Permissions chronograf.Permissions `json:"permissions"` - Links map[string]string `json:"links"` // Links are URI locations related to user - }{ - Permissions: perms, - Links: map[string]string{ - "self": fmt.Sprintf("%s/%d/permissions", httpAPISrcs, srcID), - "source": fmt.Sprintf("%s/%d", httpAPISrcs, srcID), - }, - } - encodeJSON(w, http.StatusOK, res, s.Logger) -} diff --git a/chronograf/server/permissions_test.go b/chronograf/server/permissions_test.go deleted file mode 100644 index 1d0db37fa44..00000000000 --- a/chronograf/server/permissions_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package server - -import ( - "bytes" - "context" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -func TestService_Permissions(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - TimeSeries TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - ID string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "New user for data source", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://server.local/chronograf/v1/sources/1", - ioutil.NopCloser( - bytes.NewReader([]byte(`{"name": "marty", "password": "the_lake"}`)))), - }, - fields: fields{ - UseAuth: true, - Logger: &chronograf.NoopLogger{}, - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "muh source", - Username: "name", - Password: "hunter2", - URL: "http://localhost:8086", - }, nil - }, - }, - TimeSeries: &mocks.TimeSeries{ - ConnectF: func(ctx context.Context, src *chronograf.Source) error { - return nil - }, - PermissionsF: func(ctx context.Context) chronograf.Permissions { - return chronograf.Permissions{ - { - Scope: chronograf.AllScope, - Allowed: chronograf.Allowances{"READ", "WRITE"}, - }, - } - }, - }, - }, - ID: "1", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"permissions":[{"scope":"all","allowed":["READ","WRITE"]}],"links":{"self":"/chronograf/v1/sources/1/permissions","source":"/chronograf/v1/sources/1"}} -`, - }, - } - for _, tt := range tests { - tt.args.r = tt.args.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - h := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.fields.SourcesStore, - }, - TimeSeriesClient: tt.fields.TimeSeries, - Logger: tt.fields.Logger, - UseAuth: tt.fields.UseAuth, - } - h.Permissions(tt.args.w, tt.args.r) - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. Permissions() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. Permissions() = %v, want %v", tt.name, content, tt.wantContentType) - } - if tt.wantBody != "" && string(body) != tt.wantBody { - t.Errorf("%q. Permissions() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - } -} diff --git a/chronograf/server/prefixing_redirector.go b/chronograf/server/prefixing_redirector.go deleted file mode 100644 index 0317ceb3993..00000000000 --- a/chronograf/server/prefixing_redirector.go +++ /dev/null @@ -1,34 +0,0 @@ -package server - -import ( - "net/http" -) - -type flushingResponseWriter struct { - http.ResponseWriter -} - -func (f *flushingResponseWriter) WriteHeader(status int) { - f.ResponseWriter.WriteHeader(status) -} - -// Flush is here because the underlying HTTP chunked transfer response writer -// to implement http.Flusher. Without it data is silently buffered. This -// was discovered when proxying kapacitor chunked logs. -func (f *flushingResponseWriter) Flush() { - if flusher, ok := f.ResponseWriter.(http.Flusher); ok { - flusher.Flush() - } -} - -// FlushingHandler may not actually do anything, but it was ostensibly -// implemented to flush response writers that can be flushed for the -// purposes in the comment above. -func FlushingHandler(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - iw := &flushingResponseWriter{ - ResponseWriter: w, - } - next.ServeHTTP(iw, r) - }) -} diff --git a/chronograf/server/proxy.go b/chronograf/server/proxy.go deleted file mode 100644 index ad313184a06..00000000000 --- a/chronograf/server/proxy.go +++ /dev/null @@ -1,121 +0,0 @@ -package server - -import ( - "crypto/tls" - "fmt" - "net" - "net/http" - "net/http/httputil" - "net/url" - "strings" - "time" -) - -// Proxy proxies requests to services using the path query parameter. -func (s *Service) Proxy(w http.ResponseWriter, r *http.Request) { - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - id, err := paramID("kid", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - path := r.URL.Query().Get("path") - if path == "" { - Error(w, http.StatusUnprocessableEntity, "path query parameter required", s.Logger) - return - } - - ctx := r.Context() - srv, err := s.Store.Servers(ctx).Get(ctx, id) - if err != nil || srv.SrcID != srcID { - notFound(w, id, s.Logger) - return - } - - // To preserve any HTTP query arguments to the kapacitor path, - // we concat and parse them into u. - uri := singleJoiningSlash(srv.URL, path) - u, err := url.Parse(uri) - if err != nil { - msg := fmt.Sprintf("Error parsing kapacitor url: %v", err) - Error(w, http.StatusUnprocessableEntity, msg, s.Logger) - return - } - - director := func(req *http.Request) { - // Set the Host header of the original Kapacitor URL - req.Host = u.Host - req.URL = u - - // Because we are acting as a proxy, kapacitor needs to have the basic auth information set as - // a header directly - if srv.Username != "" && srv.Password != "" { - req.SetBasicAuth(srv.Username, srv.Password) - } - } - - // Without a FlushInterval the HTTP Chunked response for kapacitor logs is - // buffered and flushed every 30 seconds. - proxy := &httputil.ReverseProxy{ - Director: director, - FlushInterval: time.Second, - } - - // The connection to kapacitor is using a self-signed certificate. - // This modifies uses the same values as http.DefaultTransport but specifies - // InsecureSkipVerify - if srv.InsecureSkipVerify { - proxy.Transport = &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - }).DialContext, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - } - proxy.ServeHTTP(w, r) -} - -// ProxyPost proxies POST to service -func (s *Service) ProxyPost(w http.ResponseWriter, r *http.Request) { - s.Proxy(w, r) -} - -// ProxyPatch proxies PATCH to Service -func (s *Service) ProxyPatch(w http.ResponseWriter, r *http.Request) { - s.Proxy(w, r) -} - -// ProxyGet proxies GET to service -func (s *Service) ProxyGet(w http.ResponseWriter, r *http.Request) { - s.Proxy(w, r) -} - -// ProxyDelete proxies DELETE to service -func (s *Service) ProxyDelete(w http.ResponseWriter, r *http.Request) { - s.Proxy(w, r) -} - -func singleJoiningSlash(a, b string) string { - aslash := strings.HasSuffix(a, "/") - bslash := strings.HasPrefix(b, "/") - if aslash && bslash { - return a + b[1:] - } - if !aslash && !bslash { - return a + "/" + b - } - return a + b -} diff --git a/chronograf/server/queries.go b/chronograf/server/queries.go deleted file mode 100644 index 38ab656a33e..00000000000 --- a/chronograf/server/queries.go +++ /dev/null @@ -1,134 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - "time" - - "golang.org/x/net/context" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/influx" - "github.com/influxdata/influxdb/v2/chronograf/influx/queries" -) - -// QueryRequest is query that will be converted to a queryConfig -type QueryRequest struct { - ID string `json:"id"` - Query string `json:"query"` -} - -// QueriesRequest converts all queries to queryConfigs with the help -// of the template variables -type QueriesRequest struct { - Queries []QueryRequest `json:"queries"` - TemplateVars []chronograf.TemplateVar `json:"tempVars,omitempty"` -} - -// QueryResponse is the return result of a QueryRequest including -// the raw query, the templated query, the queryConfig and the queryAST -type QueryResponse struct { - Duration int64 `json:"durationMs"` - ID string `json:"id"` - Query string `json:"query"` - QueryConfig chronograf.QueryConfig `json:"queryConfig"` - QueryAST *queries.SelectStatement `json:"queryAST,omitempty"` - QueryTemplated *string `json:"queryTemplated,omitempty"` -} - -// QueriesResponse is the response for a QueriesRequest -type QueriesResponse struct { - Queries []QueryResponse `json:"queries"` -} - -// Queries analyzes InfluxQL to produce front-end friendly QueryConfig -func (s *Service) Queries(w http.ResponseWriter, r *http.Request) { - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - src, err := s.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, s.Logger) - return - } - - var req QueriesRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - res := QueriesResponse{ - Queries: make([]QueryResponse, len(req.Queries)), - } - - for i, q := range req.Queries { - qr := QueryResponse{ - ID: q.ID, - Query: q.Query, - } - - qc := ToQueryConfig(q.Query) - if err := s.DefaultRP(ctx, &qc, &src); err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - qc.Shifts = []chronograf.TimeShift{} - qr.QueryConfig = qc - - if stmt, err := queries.ParseSelect(q.Query); err == nil { - qr.QueryAST = stmt - } - - if dur, err := influx.ParseTime(q.Query, time.Now()); err == nil { - ms := dur.Nanoseconds() / int64(time.Millisecond) - if ms == 0 { - ms = 1 - } - - qr.Duration = ms - } - - qr.QueryConfig.ID = q.ID - res.Queries[i] = qr - } - - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// DefaultRP will add the default retention policy to the QC if one has not been specified -func (s *Service) DefaultRP(ctx context.Context, qc *chronograf.QueryConfig, src *chronograf.Source) error { - // Only need to find the default RP IFF the qc's rp is empty - if qc.RetentionPolicy != "" { - return nil - } - - // For queries without databases, measurements, or fields we will not - // be able to find an RP - if qc.Database == "" || qc.Measurement == "" || len(qc.Fields) == 0 { - return nil - } - - db := s.Databases - if err := db.Connect(ctx, src); err != nil { - return fmt.Errorf("unable to connect to source: %v", err) - } - - rps, err := db.AllRP(ctx, qc.Database) - if err != nil { - return fmt.Errorf("unable to load RPs from DB %s: %v", qc.Database, err) - } - - for _, rp := range rps { - if rp.Default { - qc.RetentionPolicy = rp.Name - return nil - } - } - - return nil -} diff --git a/chronograf/server/queries_test.go b/chronograf/server/queries_test.go deleted file mode 100644 index c8008861e46..00000000000 --- a/chronograf/server/queries_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package server - -import ( - "bytes" - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" -) - -func TestService_Queries(t *testing.T) { - tests := []struct { - name string - SourcesStore chronograf.SourcesStore - ID string - w *httptest.ResponseRecorder - r *http.Request - want string - }{ - { - name: "bad json", - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: ID, - }, nil - }, - }, - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte(`howdy`))), - want: `{"code":400,"message":"unparsable JSON"}`, - }, - { - name: "bad id", - ID: "howdy", - w: httptest.NewRecorder(), - r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte{})), - want: `{"code":422,"message":"error converting ID howdy"}`, - }, - { - name: "query with no template vars", - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: ID, - }, nil - }, - }, - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte(`{ - "queries": [ - { - "query": "SELECT \"pingReq\" FROM db.\"monitor\".\"httpd\" WHERE time > now() - 1m", - "id": "82b60d37-251e-4afe-ac93-ca20a3642b11" - } - ]}`))), - want: `{"queries":[{"durationMs":59999,"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SELECT \"pingReq\" FROM db.\"monitor\".\"httpd\" WHERE time \u003e now() - 1m","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"db","measurement":"httpd","retentionPolicy":"monitor","fields":[{"value":"pingReq","type":"field","alias":""}],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":null,"range":{"upper":"","lower":"now() - 1m"},"shifts":[]},"queryAST":{"condition":{"expr":"binary","op":"\u003e","lhs":{"expr":"reference","val":"time"},"rhs":{"expr":"binary","op":"-","lhs":{"expr":"call","name":"now"},"rhs":{"expr":"literal","val":"1m","type":"duration"}}},"fields":[{"column":{"expr":"reference","val":"pingReq"}}],"sources":[{"database":"db","retentionPolicy":"monitor","name":"httpd","type":"measurement"}]}}]} -`, - }, - { - name: "query with unparsable query", - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, ID int) (chronograf.Source, error) { - return chronograf.Source{ - ID: ID, - }, nil - }, - }, - ID: "1", - w: httptest.NewRecorder(), - r: httptest.NewRequest("POST", "/queries", bytes.NewReader([]byte(`{ - "queries": [ - { - "query": "SHOW DATABASES", - "id": "82b60d37-251e-4afe-ac93-ca20a3642b11" - } - ]}`))), - want: `{"queries":[{"durationMs":0,"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","query":"SHOW DATABASES","queryConfig":{"id":"82b60d37-251e-4afe-ac93-ca20a3642b11","database":"","measurement":"","retentionPolicy":"","fields":[],"tags":{},"groupBy":{"time":"","tags":[]},"areTagsAccepted":false,"rawText":"SHOW DATABASES","range":null,"shifts":[]}}]} -`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.r = tt.r.WithContext(context.WithValue( - context.TODO(), - httprouter.ParamsKey, - httprouter.Params{ - { - Key: "id", - Value: tt.ID, - }, - })) - s := &Service{ - Store: &mocks.Store{ - SourcesStore: tt.SourcesStore, - }, - Logger: &mocks.TestLogger{}, - } - s.Queries(tt.w, tt.r) - got := tt.w.Body.String() - if got != tt.want { - t.Errorf("got:\n%s\nwant:\n%s\n", got, tt.want) - } - }) - } -} diff --git a/chronograf/server/queryconfig.go b/chronograf/server/queryconfig.go deleted file mode 100644 index 6f575a688f9..00000000000 --- a/chronograf/server/queryconfig.go +++ /dev/null @@ -1,51 +0,0 @@ -package server - -import ( - "fmt" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/influx" -) - -// ToQueryConfig converts InfluxQL into queryconfigs -// If influxql cannot be represented by a full query config, then, the -// query config's raw text is set to the query. -func ToQueryConfig(query string) chronograf.QueryConfig { - qc, err := influx.Convert(query) - if err == nil { - return qc - } - return chronograf.QueryConfig{ - RawText: &query, - Fields: []chronograf.Field{}, - GroupBy: chronograf.GroupBy{ - Tags: []string{}, - }, - Tags: make(map[string][]string), - } -} - -var validFieldTypes = map[string]bool{ - "func": true, - "field": true, - "integer": true, - "number": true, - "regex": true, - "wildcard": true, -} - -// ValidateQueryConfig checks any query config input -func ValidateQueryConfig(q *chronograf.QueryConfig) error { - for _, fld := range q.Fields { - invalid := fmt.Errorf(`invalid field type "%s" ; expect func, field, integer, number, regex, wildcard`, fld.Type) - if !validFieldTypes[fld.Type] { - return invalid - } - for _, arg := range fld.Args { - if !validFieldTypes[arg.Type] { - return invalid - } - } - } - return nil -} diff --git a/chronograf/server/queryconfig_test.go b/chronograf/server/queryconfig_test.go deleted file mode 100644 index c6ea49f8378..00000000000 --- a/chronograf/server/queryconfig_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package server - -import ( - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestValidateQueryConfig(t *testing.T) { - tests := []struct { - name string - q *chronograf.QueryConfig - wantErr bool - }{ - { - name: "invalid field type", - q: &chronograf.QueryConfig{ - Fields: []chronograf.Field{ - { - Type: "invalid", - }, - }, - }, - wantErr: true, - }, - { - name: "invalid field args", - q: &chronograf.QueryConfig{ - Fields: []chronograf.Field{ - { - Type: "func", - Args: []chronograf.Field{ - { - Type: "invalid", - }, - }, - }, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := ValidateQueryConfig(tt.q); (err != nil) != tt.wantErr { - t.Errorf("ValidateQueryConfig() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/chronograf/server/redoc.go b/chronograf/server/redoc.go deleted file mode 100644 index 4cc39bf27dc..00000000000 --- a/chronograf/server/redoc.go +++ /dev/null @@ -1,39 +0,0 @@ -package server - -import ( - "fmt" - "net/http" -) - -const index = ` - - - Chronograf API - - - - - - - - - - -` - -// Redoc servers the swagger JSON using the redoc package. -func Redoc(swagger string) http.HandlerFunc { - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Header().Set("Content-Type", "text/html; charset=utf-8") - rw.WriteHeader(http.StatusOK) - - _, _ = rw.Write([]byte(fmt.Sprintf(index, swagger))) - }) -} diff --git a/chronograf/server/routes.go b/chronograf/server/routes.go deleted file mode 100644 index db5144fd1cc..00000000000 --- a/chronograf/server/routes.go +++ /dev/null @@ -1,122 +0,0 @@ -package server - -import ( - "fmt" - "net/http" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" -) - -// AuthRoute are the routes for each type of OAuth2 provider -type AuthRoute struct { - Name string `json:"name"` // Name uniquely identifies the provider - Label string `json:"label"` // Label is a user-facing string to present in the UI - Login string `json:"login"` // Login is the route to the login redirect path - Logout string `json:"logout"` // Logout is the route to the logout redirect path - Callback string `json:"callback"` // Callback is the route the provider calls to exchange the code/state -} - -// AuthRoutes contains all OAuth2 provider routes. -type AuthRoutes []AuthRoute - -// Lookup searches all the routes for a specific provider -func (r *AuthRoutes) Lookup(provider string) (AuthRoute, bool) { - for _, route := range *r { - if route.Name == provider { - return route, true - } - } - return AuthRoute{}, false -} - -type getRoutesResponse struct { - Layouts string `json:"layouts"` // Location of the layouts endpoint - Users string `json:"users"` // Location of the users endpoint - AllUsers string `json:"allUsers"` // Location of the raw users endpoint - Organizations string `json:"organizations"` // Location of the organizations endpoint - Mappings string `json:"mappings"` // Location of the application mappings endpoint - Sources string `json:"sources"` // Location of the sources endpoint - Me string `json:"me"` // Location of the me endpoint - Environment string `json:"environment"` // Location of the environment endpoint - Dashboards string `json:"dashboards"` // Location of the dashboards endpoint - Config getConfigLinksResponse `json:"config"` // Location of the config endpoint and its various sections - Cells string `json:"cells"` // Location of the v2 cells - DashboardsV2 string `json:"dashboardsv2"` // Location of the v2 dashboards - Auth []AuthRoute `json:"auth"` // Location of all auth routes. - Logout *string `json:"logout,omitempty"` // Location of the logout route for all auth routes - ExternalLinks getExternalLinksResponse `json:"external"` // All external links for the client to use - OrganizationConfig getOrganizationConfigLinksResponse `json:"orgConfig"` // Location of the organization config endpoint - Flux getFluxLinksResponse `json:"flux"` -} - -// AllRoutes is a handler that returns all links to resources in Chronograf server, as well as -// external links for the client to know about, such as for JSON feeds or custom side nav buttons. -// Optionally, routes for authentication can be returned. -type AllRoutes struct { - GetPrincipal func(r *http.Request) oauth2.Principal // GetPrincipal is used to retrieve the principal on http request. - AuthRoutes []AuthRoute // Location of all auth routes. If no auth, this can be empty. - LogoutLink string // Location of the logout route for all auth routes. If no auth, this can be empty. - StatusFeed string // External link to the JSON Feed for the News Feed on the client's Status Page - CustomLinks map[string]string // Custom external links for client's User menu, as passed in via CLI/ENV - Logger chronograf.Logger -} - -// serveHTTP returns all top level routes and external links within chronograf -func (a *AllRoutes) ServeHTTP(w http.ResponseWriter, r *http.Request) { - customLinks, err := NewCustomLinks(a.CustomLinks) - if err != nil { - Error(w, http.StatusInternalServerError, err.Error(), a.Logger) - return - } - - org := "default" - if a.GetPrincipal != nil { - // If there is a principal, use the organization to populate the users routes - // otherwise use the default organization - if p := a.GetPrincipal(r); p.Organization != "" { - org = p.Organization - } - } - - routes := getRoutesResponse{ - Sources: "/chronograf/v1/sources", - Layouts: "/chronograf/v1/layouts", - Users: fmt.Sprintf("/chronograf/v1/organizations/%s/users", org), - AllUsers: "/chronograf/v1/users", - Organizations: "/chronograf/v1/organizations", - Me: "/chronograf/v1/me", - Environment: "/chronograf/v1/env", - Mappings: "/chronograf/v1/mappings", - Dashboards: "/chronograf/v1/dashboards", - DashboardsV2: "/chronograf/v2/dashboards", - Cells: "/chronograf/v2/cells", - Config: getConfigLinksResponse{ - Self: "/chronograf/v1/config", - Auth: "/chronograf/v1/config/auth", - }, - OrganizationConfig: getOrganizationConfigLinksResponse{ - Self: "/chronograf/v1/org_config", - LogViewer: "/chronograf/v1/org_config/logviewer", - }, - Auth: make([]AuthRoute, len(a.AuthRoutes)), // We want to return at least an empty array, rather than null - ExternalLinks: getExternalLinksResponse{ - StatusFeed: &a.StatusFeed, - CustomLinks: customLinks, - }, - Flux: getFluxLinksResponse{ - Self: "/chronograf/v1/flux", - AST: "/chronograf/v1/flux/ast", - Suggestions: "/chronograf/v1/flux/suggestions", - }, - } - - // The JSON response will have no field present for the LogoutLink if there is no logout link. - if a.LogoutLink != "" { - routes.Logout = &a.LogoutLink - } - - copy(routes.Auth, a.AuthRoutes) - - encodeJSON(w, http.StatusOK, routes, a.Logger) -} diff --git a/chronograf/server/routes_test.go b/chronograf/server/routes_test.go deleted file mode 100644 index 5333c2dc11f..00000000000 --- a/chronograf/server/routes_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package server - -import ( - "encoding/json" - "io/ioutil" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestAllRoutes(t *testing.T) { - logger := &chronograf.NoopLogger{} - handler := &AllRoutes{ - Logger: logger, - } - req := httptest.NewRequest("GET", "http://docbrowns-inventions.com", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, req) - - resp := w.Result() - body, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() - - if err != nil { - t.Error("TestAllRoutes not able to retrieve body") - } - var routes getRoutesResponse - if err := json.Unmarshal(body, &routes); err != nil { - t.Error("TestAllRoutes not able to unmarshal JSON response") - } - want := `{"dashboardsv2":"/chronograf/v2/dashboards","orgConfig":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"cells":"/chronograf/v2/cells","layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}} -` - - eq, err := jsonEqual(want, string(body)) - if err != nil { - t.Fatalf("error decoding json: %v", err) - } - if !eq { - t.Errorf("TestAllRoutes\nwanted\n*%s*\ngot\n*%s*", want, string(body)) - } - -} - -func TestAllRoutesWithAuth(t *testing.T) { - logger := &chronograf.NoopLogger{} - handler := &AllRoutes{ - AuthRoutes: []AuthRoute{ - { - Name: "github", - Label: "GitHub", - Login: "/oauth/github/login", - Logout: "/oauth/github/logout", - Callback: "/oauth/github/callback", - }, - }, - LogoutLink: "/oauth/logout", - Logger: logger, - } - req := httptest.NewRequest("GET", "http://docbrowns-inventions.com", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, req) - - resp := w.Result() - body, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() - - if err != nil { - t.Error("TestAllRoutesWithAuth not able to retrieve body") - } - var routes getRoutesResponse - if err := json.Unmarshal(body, &routes); err != nil { - t.Error("TestAllRoutesWithAuth not able to unmarshal JSON response") - } - want := `{"dashboardsv2":"/chronograf/v2/dashboards","orgConfig":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"cells":"/chronograf/v2/cells","layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[{"name":"github","label":"GitHub","login":"/oauth/github/login","logout":"/oauth/github/logout","callback":"/oauth/github/callback"}],"logout":"/oauth/logout","external":{"statusFeed":""},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}} -` - eq, err := jsonEqual(want, string(body)) - if err != nil { - t.Fatalf("error decoding json: %v", err) - } - if !eq { - t.Errorf("TestAllRoutesWithAuth\nwanted\n*%s*\ngot\n*%s*", want, string(body)) - } -} - -func TestAllRoutesWithExternalLinks(t *testing.T) { - statusFeedURL := "http://pineapple.life/feed.json" - customLinks := map[string]string{ - "cubeapple": "https://cube.apple", - } - logger := &chronograf.NoopLogger{} - handler := &AllRoutes{ - StatusFeed: statusFeedURL, - CustomLinks: customLinks, - Logger: logger, - } - req := httptest.NewRequest("GET", "http://docbrowns-inventions.com", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, req) - - resp := w.Result() - body, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() - - if err != nil { - t.Error("TestAllRoutesWithExternalLinks not able to retrieve body") - } - var routes getRoutesResponse - if err := json.Unmarshal(body, &routes); err != nil { - t.Error("TestAllRoutesWithExternalLinks not able to unmarshal JSON response") - } - want := `{"dashboardsv2":"/chronograf/v2/dashboards","orgConfig":{"self":"/chronograf/v1/org_config","logViewer":"/chronograf/v1/org_config/logviewer"},"cells":"/chronograf/v2/cells","layouts":"/chronograf/v1/layouts","users":"/chronograf/v1/organizations/default/users","allUsers":"/chronograf/v1/users","organizations":"/chronograf/v1/organizations","mappings":"/chronograf/v1/mappings","sources":"/chronograf/v1/sources","me":"/chronograf/v1/me","environment":"/chronograf/v1/env","dashboards":"/chronograf/v1/dashboards","config":{"self":"/chronograf/v1/config","auth":"/chronograf/v1/config/auth"},"auth":[],"external":{"statusFeed":"http://pineapple.life/feed.json","custom":[{"name":"cubeapple","url":"https://cube.apple"}]},"flux":{"ast":"/chronograf/v1/flux/ast","self":"/chronograf/v1/flux","suggestions":"/chronograf/v1/flux/suggestions"}} -` - eq, err := jsonEqual(want, string(body)) - if err != nil { - t.Fatalf("error decoding json: %v", err) - } - if !eq { - t.Errorf("TestAllRoutesWithExternalLinks\nwanted\n*%s*\ngot\n*%s*", want, string(body)) - } -} diff --git a/chronograf/server/server.go b/chronograf/server/server.go deleted file mode 100644 index 87c28cf8490..00000000000 --- a/chronograf/server/server.go +++ /dev/null @@ -1,572 +0,0 @@ -package server - -import ( - "context" - "crypto/tls" - "fmt" - "log" - "math/rand" - "net" - "net/http" - "net/url" - "os" - "path" - "regexp" - "runtime" - "strconv" - "time" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt" - idgen "github.com/influxdata/influxdb/v2/chronograf/id" - "github.com/influxdata/influxdb/v2/chronograf/influx" - "github.com/influxdata/influxdb/v2/chronograf/oauth2" - client "github.com/influxdata/usage-client/v1" - flags "github.com/jessevdk/go-flags" - "github.com/tylerb/graceful" - bbolt "go.etcd.io/bbolt" -) - -var ( - startTime time.Time -) - -func init() { - startTime = time.Now().UTC() -} - -// Server for the chronograf API -type Server struct { - Host string `long:"host" description:"The IP to listen on" default:"0.0.0.0" env:"HOST"` - Port int `long:"port" description:"The port to listen on for insecure connections, defaults to a random value" default:"8888" env:"PORT"` - - PprofEnabled bool `long:"pprof-enabled" description:"Enable the /debug/pprof/* HTTP routes" env:"PPROF_ENABLED"` - - Cert flags.Filename `long:"cert" description:"Path to PEM encoded public key certificate. " env:"TLS_CERTIFICATE"` - Key flags.Filename `long:"key" description:"Path to private key associated with given certificate. " env:"TLS_PRIVATE_KEY"` - - InfluxDBURL string `long:"influxdb-url" description:"Location of your InfluxDB instance" env:"INFLUXDB_URL"` - InfluxDBUsername string `long:"influxdb-username" description:"Username for your InfluxDB instance" env:"INFLUXDB_USERNAME"` - InfluxDBPassword string `long:"influxdb-password" description:"Password for your InfluxDB instance" env:"INFLUXDB_PASSWORD"` - - KapacitorURL string `long:"kapacitor-url" description:"Location of your Kapacitor instance" env:"KAPACITOR_URL"` - KapacitorUsername string `long:"kapacitor-username" description:"Username of your Kapacitor instance" env:"KAPACITOR_USERNAME"` - KapacitorPassword string `long:"kapacitor-password" description:"Password of your Kapacitor instance" env:"KAPACITOR_PASSWORD"` - - NewSources string `long:"new-sources" description:"Config for adding a new InfluxDB source and Kapacitor server, in JSON as an array of objects, and surrounded by single quotes. E.g. --new-sources='[{\"influxdb\":{\"name\":\"Influx 1\",\"username\":\"user1\",\"password\":\"pass1\",\"url\":\"http://localhost:8086\",\"metaUrl\":\"http://metaurl.com\",\"type\":\"influx-enterprise\",\"insecureSkipVerify\":false,\"default\":true,\"telegraf\":\"telegraf\",\"sharedSecret\":\"cubeapples\"},\"kapacitor\":{\"name\":\"Kapa 1\",\"url\":\"http://localhost:9092\",\"active\":true}}]'" env:"NEW_SOURCES" hidden:"true"` - - Develop bool `short:"d" long:"develop" description:"Run server in develop mode."` - BoltPath string `short:"b" long:"bolt-path" description:"Full path to boltDB file (e.g. './chronograf-v1.db')" env:"BOLT_PATH" default:"chronograf-v1.db"` - CannedPath string `short:"c" long:"canned-path" description:"Path to directory of pre-canned application layouts (/usr/share/chronograf/canned)" env:"CANNED_PATH" default:"canned"` - ResourcesPath string `long:"resources-path" description:"Path to directory of pre-canned dashboards, sources, kapacitors, and organizations (/usr/share/chronograf/resources)" env:"RESOURCES_PATH" default:"canned"` - TokenSecret string `short:"t" long:"token-secret" description:"Secret to sign tokens" env:"TOKEN_SECRET"` - JwksURL string `long:"jwks-url" description:"URL that returns OpenID Key Discovery JWKS document." env:"JWKS_URL"` - UseIDToken bool `long:"use-id-token" description:"Enable id_token processing." env:"USE_ID_TOKEN"` - AuthDuration time.Duration `long:"auth-duration" default:"720h" description:"Total duration of cookie life for authentication (in hours). 0 means authentication expires on browser close." env:"AUTH_DURATION"` - - 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"` - GithubOrgs []string `short:"o" long:"github-organization" description:"Github organization user is required to have active membership" env:"GH_ORGS" env-delim:","` - - GoogleClientID string `long:"google-client-id" description:"Google Client ID for OAuth 2 support" env:"GOOGLE_CLIENT_ID"` - GoogleClientSecret string `long:"google-client-secret" description:"Google Client Secret for OAuth 2 support" env:"GOOGLE_CLIENT_SECRET"` - GoogleDomains []string `long:"google-domains" description:"Google email domain user is required to have active membership" env:"GOOGLE_DOMAINS" env-delim:","` - PublicURL string `long:"public-url" description:"Full public URL used to access Chronograf from a web browser. Used for OAuth2 authentication. (http://localhost:8888)" env:"PUBLIC_URL"` - - HerokuClientID string `long:"heroku-client-id" description:"Heroku Client ID for OAuth 2 support" env:"HEROKU_CLIENT_ID"` - HerokuSecret string `long:"heroku-secret" description:"Heroku Secret for OAuth 2 support" env:"HEROKU_SECRET"` - HerokuOrganizations []string `long:"heroku-organization" description:"Heroku Organization Memberships a user is required to have for access to Chronograf (comma separated)" env:"HEROKU_ORGS" env-delim:","` - - GenericName string `long:"generic-name" description:"Generic OAuth2 name presented on the login page" env:"GENERIC_NAME"` - GenericClientID string `long:"generic-client-id" description:"Generic OAuth2 Client ID. Can be used own OAuth2 service." env:"GENERIC_CLIENT_ID"` - GenericClientSecret string `long:"generic-client-secret" description:"Generic OAuth2 Client Secret" env:"GENERIC_CLIENT_SECRET"` - GenericScopes []string `long:"generic-scopes" description:"Scopes requested by provider of web client." default:"user:email" env:"GENERIC_SCOPES" env-delim:","` - GenericDomains []string `long:"generic-domains" description:"Email domain users' email address to have (example.com)" env:"GENERIC_DOMAINS" env-delim:","` - GenericAuthURL string `long:"generic-auth-url" description:"OAuth 2.0 provider's authorization endpoint URL" env:"GENERIC_AUTH_URL"` - GenericTokenURL string `long:"generic-token-url" description:"OAuth 2.0 provider's token endpoint URL" env:"GENERIC_TOKEN_URL"` - GenericAPIURL string `long:"generic-api-url" description:"URL that returns OpenID UserInfo compatible information." env:"GENERIC_API_URL"` - GenericAPIKey string `long:"generic-api-key" description:"JSON lookup key into OpenID UserInfo. (Azure should be userPrincipalName)" default:"email" env:"GENERIC_API_KEY"` - - Auth0Domain string `long:"auth0-domain" description:"Subdomain of auth0.com used for Auth0 OAuth2 authentication" env:"AUTH0_DOMAIN"` - Auth0ClientID string `long:"auth0-client-id" description:"Auth0 Client ID for OAuth2 support" env:"AUTH0_CLIENT_ID"` - Auth0ClientSecret string `long:"auth0-client-secret" description:"Auth0 Client Secret for OAuth2 support" env:"AUTH0_CLIENT_SECRET"` - Auth0Organizations []string `long:"auth0-organizations" description:"Auth0 organizations permitted to access Chronograf (comma separated)" env:"AUTH0_ORGS" env-delim:","` - Auth0SuperAdminOrg string `long:"auth0-superadmin-org" description:"Auth0 organization from which users are automatically granted SuperAdmin status" env:"AUTH0_SUPERADMIN_ORG"` - - StatusFeedURL string `long:"status-feed-url" description:"URL of a JSON Feed to display as a News Feed on the client Status page." default:"https://www.influxdata.com/feed/json" env:"STATUS_FEED_URL"` - CustomLinks map[string]string `long:"custom-link" description:"Custom link to be added to the client User menu. Multiple links can be added by using multiple of the same flag with different 'name:url' values, or as an environment variable with comma-separated 'name:url' values. E.g. via flags: '--custom-link=InfluxData:https://www.influxdata.com --custom-link=Chronograf:https://github.com/influxdata/influxdb/chronograf'. E.g. via environment variable: 'export CUSTOM_LINKS=InfluxData:https://www.influxdata.com,Chronograf:https://github.com/influxdata/influxdb/chronograf'" env:"CUSTOM_LINKS" env-delim:","` - TelegrafSystemInterval time.Duration `long:"telegraf-system-interval" default:"1m" description:"Duration used in the GROUP BY time interval for the hosts list" env:"TELEGRAF_SYSTEM_INTERVAL"` - - 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. (Note: PREFIX_ROUTES has been deprecated. Now, if basepath is set, all routes will be prefixed with it.)" env:"BASE_PATH"` - ShowVersion bool `short:"v" long:"version" description:"Show Chronograf version info"` - BuildInfo chronograf.BuildInfo - Listener net.Listener - handler http.Handler -} - -func provide(p oauth2.Provider, m oauth2.Mux, ok func() bool) func(func(oauth2.Provider, oauth2.Mux)) { - return func(configure func(oauth2.Provider, oauth2.Mux)) { - if ok() { - configure(p, m) - } - } -} - -// UseGithub validates the CLI parameters to enable github oauth support -func (s *Server) UseGithub() bool { - return s.TokenSecret != "" && s.GithubClientID != "" && s.GithubClientSecret != "" -} - -// UseGoogle validates the CLI parameters to enable google oauth support -func (s *Server) UseGoogle() bool { - return s.TokenSecret != "" && s.GoogleClientID != "" && s.GoogleClientSecret != "" && s.PublicURL != "" -} - -// UseHeroku validates the CLI parameters to enable heroku oauth support -func (s *Server) UseHeroku() bool { - return s.TokenSecret != "" && s.HerokuClientID != "" && s.HerokuSecret != "" -} - -// UseAuth0 validates the CLI parameters to enable Auth0 oauth support -func (s *Server) UseAuth0() bool { - return s.Auth0ClientID != "" && s.Auth0ClientSecret != "" -} - -// UseGenericOAuth2 validates the CLI parameters to enable generic oauth support -func (s *Server) UseGenericOAuth2() bool { - return s.TokenSecret != "" && s.GenericClientID != "" && - s.GenericClientSecret != "" && s.GenericAuthURL != "" && - s.GenericTokenURL != "" -} - -func (s *Server) githubOAuth(logger chronograf.Logger, auth oauth2.Authenticator) (oauth2.Provider, oauth2.Mux, func() bool) { - gh := oauth2.Github{ - ClientID: s.GithubClientID, - ClientSecret: s.GithubClientSecret, - Orgs: s.GithubOrgs, - Logger: logger, - } - jwt := oauth2.NewJWT(s.TokenSecret, s.JwksURL) - ghMux := oauth2.NewAuthMux(&gh, auth, jwt, s.Basepath, logger, s.UseIDToken) - return &gh, ghMux, s.UseGithub -} - -func (s *Server) googleOAuth(logger chronograf.Logger, auth oauth2.Authenticator) (oauth2.Provider, oauth2.Mux, func() bool) { - redirectURL := s.PublicURL + s.Basepath + "/oauth/google/callback" - google := oauth2.Google{ - ClientID: s.GoogleClientID, - ClientSecret: s.GoogleClientSecret, - Domains: s.GoogleDomains, - RedirectURL: redirectURL, - Logger: logger, - } - jwt := oauth2.NewJWT(s.TokenSecret, s.JwksURL) - goMux := oauth2.NewAuthMux(&google, auth, jwt, s.Basepath, logger, s.UseIDToken) - return &google, goMux, s.UseGoogle -} - -func (s *Server) herokuOAuth(logger chronograf.Logger, auth oauth2.Authenticator) (oauth2.Provider, oauth2.Mux, func() bool) { - heroku := oauth2.Heroku{ - ClientID: s.HerokuClientID, - ClientSecret: s.HerokuSecret, - Organizations: s.HerokuOrganizations, - Logger: logger, - } - jwt := oauth2.NewJWT(s.TokenSecret, s.JwksURL) - hMux := oauth2.NewAuthMux(&heroku, auth, jwt, s.Basepath, logger, s.UseIDToken) - return &heroku, hMux, s.UseHeroku -} - -func (s *Server) genericOAuth(logger chronograf.Logger, auth oauth2.Authenticator) (oauth2.Provider, oauth2.Mux, func() bool) { - gen := oauth2.Generic{ - PageName: s.GenericName, - ClientID: s.GenericClientID, - ClientSecret: s.GenericClientSecret, - RequiredScopes: s.GenericScopes, - Domains: s.GenericDomains, - RedirectURL: s.genericRedirectURL(), - AuthURL: s.GenericAuthURL, - TokenURL: s.GenericTokenURL, - APIURL: s.GenericAPIURL, - APIKey: s.GenericAPIKey, - Logger: logger, - } - jwt := oauth2.NewJWT(s.TokenSecret, s.JwksURL) - genMux := oauth2.NewAuthMux(&gen, auth, jwt, s.Basepath, logger, s.UseIDToken) - return &gen, genMux, s.UseGenericOAuth2 -} - -func (s *Server) auth0OAuth(logger chronograf.Logger, auth oauth2.Authenticator) (oauth2.Provider, oauth2.Mux, func() bool) { - redirectPath := path.Join(s.Basepath, "oauth", "auth0", "callback") - redirectURL, err := url.Parse(s.PublicURL) - if err != nil { - logger.Error("Error parsing public URL: err:", err) - return &oauth2.Auth0{}, &oauth2.AuthMux{}, func() bool { return false } - } - redirectURL.Path = redirectPath - - auth0, err := oauth2.NewAuth0(s.Auth0Domain, s.Auth0ClientID, s.Auth0ClientSecret, redirectURL.String(), s.Auth0Organizations, logger) - - jwt := oauth2.NewJWT(s.TokenSecret, s.JwksURL) - genMux := oauth2.NewAuthMux(&auth0, auth, jwt, s.Basepath, logger, s.UseIDToken) - - if err != nil { - logger.Error("Error parsing Auth0 domain: err:", err) - return &auth0, genMux, func() bool { return false } - } - return &auth0, genMux, s.UseAuth0 -} - -func (s *Server) genericRedirectURL() string { - if s.PublicURL == "" { - return "" - } - - genericName := "generic" - if s.GenericName != "" { - genericName = s.GenericName - } - - publicURL, err := url.Parse(s.PublicURL) - if err != nil { - return "" - } - - publicURL.Path = path.Join(publicURL.Path, s.Basepath, "oauth", genericName, "callback") - return publicURL.String() -} - -func (s *Server) useAuth() bool { - return s.UseGithub() || s.UseGoogle() || s.UseHeroku() || s.UseGenericOAuth2() || s.UseAuth0() -} - -func (s *Server) useTLS() bool { - return s.Cert != "" -} - -// NewListener will an http or https listener depending useTLS() -func (s *Server) NewListener() (net.Listener, error) { - addr := net.JoinHostPort(s.Host, strconv.Itoa(s.Port)) - if !s.useTLS() { - listener, err := net.Listen("tcp", addr) - if err != nil { - return nil, err - } - return listener, nil - } - - // If no key specified, therefore, we assume it is in the cert - if s.Key == "" { - s.Key = s.Cert - } - - cert, err := tls.LoadX509KeyPair(string(s.Cert), string(s.Key)) - if err != nil { - return nil, err - } - - listener, err := tls.Listen("tcp", addr, &tls.Config{ - Certificates: []tls.Certificate{cert}, - }) - - if err != nil { - return nil, err - } - return listener, nil -} - -type builders struct { - Layouts LayoutBuilder - Sources SourcesBuilder - Kapacitors KapacitorBuilder - Dashboards DashboardBuilder - Organizations OrganizationBuilder -} - -func (s *Server) newBuilders(logger chronograf.Logger) builders { - return builders{ - Layouts: &MultiLayoutBuilder{ - Logger: logger, - UUID: &idgen.UUID{}, - CannedPath: s.CannedPath, - }, - Dashboards: &MultiDashboardBuilder{ - Logger: logger, - ID: idgen.NewTime(), - Path: s.ResourcesPath, - }, - Sources: &MultiSourceBuilder{ - InfluxDBURL: s.InfluxDBURL, - InfluxDBUsername: s.InfluxDBUsername, - InfluxDBPassword: s.InfluxDBPassword, - Logger: logger, - ID: idgen.NewTime(), - Path: s.ResourcesPath, - }, - Kapacitors: &MultiKapacitorBuilder{ - KapacitorURL: s.KapacitorURL, - KapacitorUsername: s.KapacitorUsername, - KapacitorPassword: s.KapacitorPassword, - Logger: logger, - ID: idgen.NewTime(), - Path: s.ResourcesPath, - }, - Organizations: &MultiOrganizationBuilder{ - Logger: logger, - Path: s.ResourcesPath, - }, - } -} - -// Serve starts and runs the chronograf server -func (s *Server) Serve(ctx context.Context) error { - logger := &chronograf.NoopLogger{} - _, err := NewCustomLinks(s.CustomLinks) - if err != nil { - logger. - WithField("component", "server"). - WithField("CustomLink", "invalid"). - Error(err) - return err - } - service := openService(ctx, s.BuildInfo, s.BoltPath, s.newBuilders(logger), logger, s.useAuth()) - service.SuperAdminProviderGroups = superAdminProviderGroups{ - auth0: s.Auth0SuperAdminOrg, - } - service.Env = chronograf.Environment{ - TelegrafSystemInterval: s.TelegrafSystemInterval, - } - - if !validBasepath(s.Basepath) { - err := fmt.Errorf("invalid basepath, must follow format \"/mybasepath\"") - logger. - WithField("component", "server"). - WithField("basepath", "invalid"). - Error(err) - return err - } - - providerFuncs := []func(func(oauth2.Provider, oauth2.Mux)){} - - auth := oauth2.NewCookieJWT(s.TokenSecret, s.AuthDuration) - providerFuncs = append(providerFuncs, provide(s.githubOAuth(logger, auth))) - providerFuncs = append(providerFuncs, provide(s.googleOAuth(logger, auth))) - providerFuncs = append(providerFuncs, provide(s.herokuOAuth(logger, auth))) - providerFuncs = append(providerFuncs, provide(s.genericOAuth(logger, auth))) - providerFuncs = append(providerFuncs, provide(s.auth0OAuth(logger, auth))) - - s.handler = NewMux(MuxOpts{ - Develop: s.Develop, - Auth: auth, - Logger: logger, - UseAuth: s.useAuth(), - ProviderFuncs: providerFuncs, - Basepath: s.Basepath, - StatusFeedURL: s.StatusFeedURL, - CustomLinks: s.CustomLinks, - }, service) - - // Add chronograf's version header to all requests - s.handler = Version(s.BuildInfo.Version, s.handler) - - if s.useTLS() { - // Add HSTS to instruct all browsers to change from http to https - s.handler = HSTS(s.handler) - } - - listener, err := s.NewListener() - if err != nil { - logger. - WithField("component", "server"). - Error(err) - return err - } - s.Listener = listener - - // Using a log writer for http server logging - w := logger.Writer() - defer w.Close() - stdLog := log.New(w, "", 0) - - // TODO: Remove graceful when changing to go 1.8 - httpServer := &graceful.Server{ - Server: &http.Server{ - ErrorLog: stdLog, - Handler: s.handler, - }, - Logger: stdLog, - TCPKeepAlive: 5 * time.Second, - } - httpServer.SetKeepAlivesEnabled(true) - - if !s.ReportingDisabled { - go reportUsageStats(s.BuildInfo, logger) - } - scheme := "http" - if s.useTLS() { - scheme = "https" - } - logger. - WithField("component", "server"). - Info("Serving chronograf at ", scheme, "://", s.Listener.Addr()) - - if err := httpServer.Serve(s.Listener); err != nil { - logger. - WithField("component", "server"). - Error(err) - return err - } - - logger. - WithField("component", "server"). - Info("Stopped serving chronograf at ", scheme, "://", s.Listener.Addr()) - - return nil -} - -func NewServiceV2(ctx context.Context, d *bbolt.DB) (*Service, error) { - db := bolt.NewClient() - db.WithDB(d) - - if err := db.Open(ctx, nil, chronograf.BuildInfo{}); err != nil { - return nil, err - } - - logger := &chronograf.NoopLogger{} - - return &Service{ - TimeSeriesClient: &InfluxClient{}, - Store: &DirectStore{ - LayoutsStore: db.LayoutsStore, - DashboardsStore: db.DashboardsStore, - SourcesStore: db.SourcesStore, - ServersStore: db.ServersStore, - OrganizationsStore: db.OrganizationsStore, - UsersStore: db.UsersStore, - ConfigStore: db.ConfigStore, - MappingsStore: db.MappingsStore, - OrganizationConfigStore: db.OrganizationConfigStore, - }, - // TODO(desa): what to do about logger - Logger: logger, - Databases: &influx.Client{ - Logger: logger, - }, - }, nil -} - -func openService(ctx context.Context, buildInfo chronograf.BuildInfo, boltPath string, builder builders, logger chronograf.Logger, useAuth bool) Service { - db := bolt.NewClient() - db.Path = boltPath - - if err := db.Open(ctx, logger, buildInfo, bolt.WithBackup()); err != nil { - logger. - WithField("component", "boltstore"). - Error(err) - os.Exit(1) - } - - layouts, err := builder.Layouts.Build(db.LayoutsStore) - if err != nil { - logger. - WithField("component", "LayoutsStore"). - Error("Unable to construct a MultiLayoutsStore", err) - os.Exit(1) - } - - dashboards, err := builder.Dashboards.Build(db.DashboardsStore) - if err != nil { - logger. - WithField("component", "DashboardsStore"). - Error("Unable to construct a MultiDashboardsStore", err) - os.Exit(1) - } - sources, err := builder.Sources.Build(db.SourcesStore) - if err != nil { - logger. - WithField("component", "SourcesStore"). - Error("Unable to construct a MultiSourcesStore", err) - os.Exit(1) - } - - kapacitors, err := builder.Kapacitors.Build(db.ServersStore) - if err != nil { - logger. - WithField("component", "KapacitorStore"). - Error("Unable to construct a MultiKapacitorStore", err) - os.Exit(1) - } - - organizations, err := builder.Organizations.Build(db.OrganizationsStore) - if err != nil { - logger. - WithField("component", "OrganizationsStore"). - Error("Unable to construct a MultiOrganizationStore", err) - os.Exit(1) - } - - return Service{ - TimeSeriesClient: &InfluxClient{}, - Store: &Store{ - LayoutsStore: layouts, - DashboardsStore: dashboards, - SourcesStore: sources, - ServersStore: kapacitors, - OrganizationsStore: organizations, - UsersStore: db.UsersStore, - ConfigStore: db.ConfigStore, - MappingsStore: db.MappingsStore, - OrganizationConfigStore: db.OrganizationConfigStore, - }, - Logger: logger, - UseAuth: useAuth, - Databases: &influx.Client{Logger: logger}, - } -} - -// reportUsageStats starts periodic server reporting. -func reportUsageStats(bi chronograf.BuildInfo, logger chronograf.Logger) { - rand.Seed(time.Now().UTC().UnixNano()) - serverID := strconv.FormatUint(uint64(rand.Int63()), 10) - reporter := client.New("") - values := client.Values{ - "os": runtime.GOOS, - "arch": runtime.GOARCH, - "version": bi.Version, - "cluster_id": serverID, - "uptime": time.Since(startTime).Seconds(), - } - l := logger.WithField("component", "usage"). - WithField("reporting_addr", reporter.URL). - WithField("freq", "24h"). - WithField("stats", "os,arch,version,cluster_id,uptime") - l.Info("Reporting usage stats") - _, _ = reporter.Save(clientUsage(values)) - - ticker := time.NewTicker(24 * time.Hour) - defer ticker.Stop() - for { - <-ticker.C - values["uptime"] = time.Since(startTime).Seconds() - l.Debug("Reporting usage stats") - go reporter.Save(clientUsage(values)) - } -} - -func clientUsage(values client.Values) *client.Usage { - return &client.Usage{ - Product: "chronograf-ng", - Data: []client.UsageData{ - { - Values: values, - }, - }, - } -} - -func validBasepath(basepath string) bool { - re := regexp.MustCompile(`(\/{1}[\w-]+)+`) - return re.ReplaceAllLiteralString(basepath, "") == "" -} diff --git a/chronograf/server/server_test.go b/chronograf/server/server_test.go deleted file mode 100644 index 9a8591cde0e..00000000000 --- a/chronograf/server/server_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package server - -import ( - "context" - "net/http" - "testing" - - "github.com/bouk/httprouter" -) - -// WithContext is a helper function to cut down on boilerplate in server test files -func WithContext(ctx context.Context, r *http.Request, kv map[string]string) *http.Request { - params := make(httprouter.Params, 0, len(kv)) - for k, v := range kv { - params = append(params, httprouter.Param{ - Key: k, - Value: v, - }) - } - return r.WithContext(httprouter.WithParams(ctx, params)) -} - -func Test_validBasepath(t *testing.T) { - type args struct { - basepath string - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "Basepath can be empty", - args: args{ - basepath: "", - }, - want: true, - }, - { - name: "Basepath is not empty and valid", - args: args{ - basepath: "/russ", - }, - want: true, - }, - { - name: "Basepath can include numbers, hyphens, and underscores", - args: args{ - basepath: "/3shishka-bob/-rus4s_rus-1_s-", - }, - want: true, - }, - { - name: "Basepath is not empty and invalid - no slashes", - args: args{ - basepath: "russ", - }, - want: false, - }, - { - name: "Basepath is not empty and invalid - extra slashes", - args: args{ - basepath: "//russ//", - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := validBasepath(tt.args.basepath); got != tt.want { - t.Errorf("validBasepath() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/chronograf/server/service.go b/chronograf/server/service.go deleted file mode 100644 index c98750657ce..00000000000 --- a/chronograf/server/service.go +++ /dev/null @@ -1,60 +0,0 @@ -package server - -import ( - "context" - "strings" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/enterprise" - "github.com/influxdata/influxdb/v2/chronograf/influx" -) - -// Service handles REST calls to the persistence -type Service struct { - Store DataStore - TimeSeriesClient TimeSeriesClient - Logger chronograf.Logger - UseAuth bool - SuperAdminProviderGroups superAdminProviderGroups - Env chronograf.Environment - Databases chronograf.Databases -} - -type superAdminProviderGroups struct { - auth0 string -} - -// TimeSeriesClient returns the correct client for a time series database. -type TimeSeriesClient interface { - New(chronograf.Source, chronograf.Logger) (chronograf.TimeSeries, error) -} - -// ErrorMessage is the error response format for all service errors -type ErrorMessage struct { - Code int `json:"code"` - Message string `json:"message"` -} - -// TimeSeries returns a new client connected to a time series database -func (s *Service) TimeSeries(src chronograf.Source) (chronograf.TimeSeries, error) { - return s.TimeSeriesClient.New(src, s.Logger) -} - -// InfluxClient returns a new client to connect to OSS or Enterprise -type InfluxClient struct{} - -// New creates a client to connect to OSS or enterprise -func (c *InfluxClient) New(src chronograf.Source, logger chronograf.Logger) (chronograf.TimeSeries, error) { - client := &influx.Client{ - Logger: logger, - } - if err := client.Connect(context.TODO(), &src); err != nil { - return nil, err - } - if src.Type == chronograf.InfluxEnterprise && src.MetaURL != "" { - tls := strings.Contains(src.MetaURL, "https") - insecure := src.InsecureSkipVerify - return enterprise.NewClientWithTimeSeries(logger, src.MetaURL, influx.DefaultAuthorization(&src), tls, insecure, client) - } - return client, nil -} diff --git a/chronograf/server/services.go b/chronograf/server/services.go deleted file mode 100644 index 2457895fdae..00000000000 --- a/chronograf/server/services.go +++ /dev/null @@ -1,352 +0,0 @@ -package server - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/flux" -) - -type postServiceRequest struct { - Name *string `json:"name"` // User facing name of service instance.; Required: true - URL *string `json:"url"` // URL for the service backend (e.g. http://localhost:9092);/ Required: true - Type *string `json:"type"` // Type is the kind of service (e.g. flux); Required - Username string `json:"username,omitempty"` // Username for authentication to service - Password string `json:"password,omitempty"` - InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. - Organization string `json:"organization"` // Organization is the organization ID that resource belongs to - Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service -} - -func (p *postServiceRequest) Valid(defaultOrgID string) error { - if p.Name == nil || p.URL == nil { - return fmt.Errorf("name and url required") - } - - if p.Type == nil { - return fmt.Errorf("type required") - } - - if p.Organization == "" { - p.Organization = defaultOrgID - } - - url, err := url.ParseRequestURI(*p.URL) - if err != nil { - return fmt.Errorf("invalid source URI: %v", err) - } - if len(url.Scheme) == 0 { - return fmt.Errorf("invalid URL; no URL scheme defined") - } - - return nil -} - -type serviceLinks struct { - Proxy string `json:"proxy"` // URL location of proxy endpoint for this source - Self string `json:"self"` // Self link mapping to this resource - Source string `json:"source"` // URL location of the parent source -} - -type service struct { - ID int `json:"id,string"` // Unique identifier representing a service instance. - SrcID int `json:"sourceID,string"` // SrcID of the data source - Name string `json:"name"` // User facing name of service instance. - URL string `json:"url"` // URL for the service backend (e.g. http://localhost:9092) - Username string `json:"username,omitempty"` // Username for authentication to service - Password string `json:"password,omitempty"` - InsecureSkipVerify bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. - Type string `json:"type"` // Type is the kind of service (e.g. flux) - Metadata map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service - Links serviceLinks `json:"links"` // Links are URI locations related to service -} - -func newService(srv chronograf.Server) service { - if srv.Metadata == nil { - srv.Metadata = make(map[string]interface{}) - } - httpAPISrcs := "/chronograf/v1/sources" - return service{ - ID: srv.ID, - SrcID: srv.SrcID, - Name: srv.Name, - Username: srv.Username, - URL: srv.URL, - InsecureSkipVerify: srv.InsecureSkipVerify, - Type: srv.Type, - Metadata: srv.Metadata, - Links: serviceLinks{ - Self: fmt.Sprintf("%s/%d/services/%d", httpAPISrcs, srv.SrcID, srv.ID), - Source: fmt.Sprintf("%s/%d", httpAPISrcs, srv.SrcID), - Proxy: fmt.Sprintf("%s/%d/services/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID), - }, - } -} - -type services struct { - Services []service `json:"services"` -} - -// NewService adds valid service store store. -func (s *Service) NewService(w http.ResponseWriter, r *http.Request) { - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - _, err = s.Store.Sources(ctx).Get(ctx, srcID) - if err != nil { - notFound(w, srcID, s.Logger) - return - } - - var req postServiceRequest - if err = json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - defaultOrg, err := s.Store.Organizations(ctx).DefaultOrganization(ctx) - if err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - if err := req.Valid(defaultOrg.ID); err != nil { - invalidData(w, err, s.Logger) - return - } - - if req.Type != nil && req.URL != nil && *req.Type == "flux" { - err := pingFlux(ctx, *req.URL, req.InsecureSkipVerify) - if err != nil { - msg := fmt.Sprintf("Unable to reach flux %s: %v", *req.URL, err) - Error(w, http.StatusGatewayTimeout, msg, s.Logger) - return - } - } - - srv := chronograf.Server{ - SrcID: srcID, - Name: *req.Name, - Username: req.Username, - Password: req.Password, - InsecureSkipVerify: req.InsecureSkipVerify, - URL: *req.URL, - Organization: req.Organization, - Type: *req.Type, - Metadata: req.Metadata, - } - - if srv, err = s.Store.Servers(ctx).Add(ctx, srv); err != nil { - msg := fmt.Errorf("error storing service %v: %v", req, err) - unknownErrorWithMessage(w, msg, s.Logger) - return - } - - res := newService(srv) - location(w, res.Links.Self) - encodeJSON(w, http.StatusCreated, res, s.Logger) -} - -// Services retrieves all services from store. -func (s *Service) Services(w http.ResponseWriter, r *http.Request) { - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - mrSrvs, err := s.Store.Servers(ctx).All(ctx) - if err != nil { - Error(w, http.StatusInternalServerError, "Error loading services", s.Logger) - return - } - - srvs := []service{} - for _, srv := range mrSrvs { - if srv.SrcID == srcID && srv.Type != "" { - srvs = append(srvs, newService(srv)) - } - } - - res := services{ - Services: srvs, - } - - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// ServiceID retrieves a service with ID from store. -func (s *Service) ServiceID(w http.ResponseWriter, r *http.Request) { - id, err := paramID("kid", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - srv, err := s.Store.Servers(ctx).Get(ctx, id) - if err != nil || srv.SrcID != srcID || srv.Type == "" { - notFound(w, id, s.Logger) - return - } - - res := newService(srv) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// RemoveService deletes service from store. -func (s *Service) RemoveService(w http.ResponseWriter, r *http.Request) { - id, err := paramID("kid", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - srv, err := s.Store.Servers(ctx).Get(ctx, id) - if err != nil || srv.SrcID != srcID || srv.Type == "" { - notFound(w, id, s.Logger) - return - } - - if err = s.Store.Servers(ctx).Delete(ctx, srv); err != nil { - unknownErrorWithMessage(w, err, s.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -type patchServiceRequest struct { - Name *string `json:"name,omitempty"` // User facing name of service instance. - Type *string `json:"type,omitempty"` // Type is the kind of service (e.g. flux) - URL *string `json:"url,omitempty"` // URL for the service - Username *string `json:"username,omitempty"` // Username for service auth - Password *string `json:"password,omitempty"` - InsecureSkipVerify *bool `json:"insecureSkipVerify"` // InsecureSkipVerify as true means any certificate presented by the service is accepted. - Metadata *map[string]interface{} `json:"metadata"` // Metadata is any other data that the frontend wants to store about this service -} - -func (p *patchServiceRequest) Valid() error { - if p.URL != nil { - url, err := url.ParseRequestURI(*p.URL) - if err != nil { - return fmt.Errorf("invalid service URI: %v", err) - } - if len(url.Scheme) == 0 { - return fmt.Errorf("invalid URL; no URL scheme defined") - } - } - - if p.Type != nil && *p.Type == "" { - return fmt.Errorf("invalid type; type must not be an empty string") - } - - return nil -} - -// UpdateService incrementally updates a service definition in the store -func (s *Service) UpdateService(w http.ResponseWriter, r *http.Request) { - id, err := paramID("kid", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - srcID, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - srv, err := s.Store.Servers(ctx).Get(ctx, id) - if err != nil || srv.SrcID != srcID || srv.Type == "" { - notFound(w, id, s.Logger) - return - } - - var req patchServiceRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := req.Valid(); err != nil { - invalidData(w, err, s.Logger) - return - } - - if req.Name != nil { - srv.Name = *req.Name - } - if req.Type != nil { - srv.Type = *req.Type - } - if req.URL != nil { - srv.URL = *req.URL - } - if req.Password != nil { - srv.Password = *req.Password - } - if req.Username != nil { - srv.Username = *req.Username - } - if req.InsecureSkipVerify != nil { - srv.InsecureSkipVerify = *req.InsecureSkipVerify - } - if req.Metadata != nil { - srv.Metadata = *req.Metadata - } - - if srv.Type == "flux" { - err := pingFlux(ctx, srv.URL, srv.InsecureSkipVerify) - if err != nil { - msg := fmt.Sprintf("Unable to reach flux %s: %v", srv.URL, err) - Error(w, http.StatusGatewayTimeout, msg, s.Logger) - return - } - } - - if err := s.Store.Servers(ctx).Update(ctx, srv); err != nil { - msg := fmt.Sprintf("Error updating service ID %d", id) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - res := newService(srv) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -func pingFlux(ctx context.Context, address string, insecureSkipVerify bool) error { - url, err := url.ParseRequestURI(address) - if err != nil { - return fmt.Errorf("invalid service URI: %v", err) - } - client := &flux.Client{ - URL: url, - InsecureSkipVerify: insecureSkipVerify, - } - return client.Ping(ctx) -} diff --git a/chronograf/server/stores.go b/chronograf/server/stores.go deleted file mode 100644 index b6918c34d06..00000000000 --- a/chronograf/server/stores.go +++ /dev/null @@ -1,289 +0,0 @@ -package server - -import ( - "context" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/noop" - "github.com/influxdata/influxdb/v2/chronograf/organizations" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -// hasOrganizationContext retrieves organization specified on context -// under the organizations.ContextKey -func hasOrganizationContext(ctx context.Context) (string, bool) { - // prevents panic in case of nil context - if ctx == nil { - return "", false - } - orgID, ok := ctx.Value(organizations.ContextKey).(string) - // should never happen - if !ok { - return "", false - } - if orgID == "" { - return "", false - } - return orgID, true -} - -// hasRoleContext retrieves organization specified on context -// under the organizations.ContextKey -func hasRoleContext(ctx context.Context) (string, bool) { - // prevents panic in case of nil context - if ctx == nil { - return "", false - } - role, ok := ctx.Value(roles.ContextKey).(string) - // should never happen - if !ok { - return "", false - } - switch role { - case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName: - return role, true - default: - return "", false - } -} - -type userContextKey string - -// UserContextKey is the context key for retrieving the user off of context -const UserContextKey = userContextKey("user") - -// hasUserContext specifies if the context contains -// the UserContextKey and that the value stored there is chronograf.User -func hasUserContext(ctx context.Context) (*chronograf.User, bool) { - // prevents panic in case of nil context - if ctx == nil { - return nil, false - } - u, ok := ctx.Value(UserContextKey).(*chronograf.User) - // should never happen - if !ok { - return nil, false - } - if u == nil { - return nil, false - } - return u, true -} - -// hasSuperAdminContext specifies if the context contains -// the UserContextKey user is a super admin -func hasSuperAdminContext(ctx context.Context) bool { - u, ok := hasUserContext(ctx) - if !ok { - return false - } - return u.SuperAdmin -} - -// DataStore is collection of resources that are used by the Service -// Abstracting this into an interface was useful for isolated testing -type DataStore interface { - Sources(ctx context.Context) chronograf.SourcesStore - Servers(ctx context.Context) chronograf.ServersStore - Layouts(ctx context.Context) chronograf.LayoutsStore - Users(ctx context.Context) chronograf.UsersStore - Organizations(ctx context.Context) chronograf.OrganizationsStore - Mappings(ctx context.Context) chronograf.MappingsStore - Dashboards(ctx context.Context) chronograf.DashboardsStore - Config(ctx context.Context) chronograf.ConfigStore - OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore -} - -// ensure that Store implements a DataStore -var _ DataStore = &Store{} - -// Store implements the DataStore interface -type Store struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - MappingsStore chronograf.MappingsStore - OrganizationsStore chronograf.OrganizationsStore - ConfigStore chronograf.ConfigStore - OrganizationConfigStore chronograf.OrganizationConfigStore -} - -// Sources returns a noop.SourcesStore if the context has no organization specified -// and an organization.SourcesStore otherwise. -func (s *Store) Sources(ctx context.Context) chronograf.SourcesStore { - if isServer := hasServerContext(ctx); isServer { - return s.SourcesStore - } - if org, ok := hasOrganizationContext(ctx); ok { - return organizations.NewSourcesStore(s.SourcesStore, org) - } - - return &noop.SourcesStore{} -} - -// Servers returns a noop.ServersStore if the context has no organization specified -// and an organization.ServersStore otherwise. -func (s *Store) Servers(ctx context.Context) chronograf.ServersStore { - if isServer := hasServerContext(ctx); isServer { - return s.ServersStore - } - if org, ok := hasOrganizationContext(ctx); ok { - return organizations.NewServersStore(s.ServersStore, org) - } - - return &noop.ServersStore{} -} - -// Layouts returns all layouts in the underlying layouts store. -func (s *Store) Layouts(ctx context.Context) chronograf.LayoutsStore { - return s.LayoutsStore -} - -// Users returns a chronograf.UsersStore. -// If the context is a server context, then the underlying chronograf.UsersStore -// is returned. -// If there is an organization specified on context, then an organizations.UsersStore -// is returned. -// If neither are specified, a noop.UsersStore is returned. -func (s *Store) Users(ctx context.Context) chronograf.UsersStore { - if isServer := hasServerContext(ctx); isServer { - return s.UsersStore - } - if org, ok := hasOrganizationContext(ctx); ok { - return organizations.NewUsersStore(s.UsersStore, org) - } - - return &noop.UsersStore{} -} - -// Dashboards returns a noop.DashboardsStore if the context has no organization specified -// and an organization.DashboardsStore otherwise. -func (s *Store) Dashboards(ctx context.Context) chronograf.DashboardsStore { - if isServer := hasServerContext(ctx); isServer { - return s.DashboardsStore - } - if org, ok := hasOrganizationContext(ctx); ok { - return organizations.NewDashboardsStore(s.DashboardsStore, org) - } - - return &noop.DashboardsStore{} -} - -// OrganizationConfig returns a noop.OrganizationConfigStore if the context has no organization specified -// and an organization.OrganizationConfigStore otherwise. -func (s *Store) OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore { - if orgID, ok := hasOrganizationContext(ctx); ok { - return organizations.NewOrganizationConfigStore(s.OrganizationConfigStore, orgID) - } - - return &noop.OrganizationConfigStore{} -} - -// Organizations returns the underlying OrganizationsStore. -func (s *Store) Organizations(ctx context.Context) chronograf.OrganizationsStore { - if isServer := hasServerContext(ctx); isServer { - return s.OrganizationsStore - } - if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin { - return s.OrganizationsStore - } - if org, ok := hasOrganizationContext(ctx); ok { - return organizations.NewOrganizationsStore(s.OrganizationsStore, org) - } - return &noop.OrganizationsStore{} -} - -// Config returns the underlying ConfigStore. -func (s *Store) Config(ctx context.Context) chronograf.ConfigStore { - if isServer := hasServerContext(ctx); isServer { - return s.ConfigStore - } - if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin { - return s.ConfigStore - } - - return &noop.ConfigStore{} -} - -// Mappings returns the underlying MappingsStore. -func (s *Store) Mappings(ctx context.Context) chronograf.MappingsStore { - if isServer := hasServerContext(ctx); isServer { - return s.MappingsStore - } - if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin { - return s.MappingsStore - } - return &noop.MappingsStore{} -} - -// ensure that DirectStore implements a DataStore -var _ DataStore = &DirectStore{} - -// Store implements the DataStore interface -type DirectStore struct { - SourcesStore chronograf.SourcesStore - ServersStore chronograf.ServersStore - LayoutsStore chronograf.LayoutsStore - UsersStore chronograf.UsersStore - DashboardsStore chronograf.DashboardsStore - MappingsStore chronograf.MappingsStore - OrganizationsStore chronograf.OrganizationsStore - ConfigStore chronograf.ConfigStore - OrganizationConfigStore chronograf.OrganizationConfigStore -} - -// Sources returns a noop.SourcesStore if the context has no organization specified -// and an organization.SourcesStore otherwise. -func (s *DirectStore) Sources(ctx context.Context) chronograf.SourcesStore { - return s.SourcesStore -} - -// Servers returns a noop.ServersStore if the context has no organization specified -// and an organization.ServersStore otherwise. -func (s *DirectStore) Servers(ctx context.Context) chronograf.ServersStore { - return s.ServersStore -} - -// Layouts returns all layouts in the underlying layouts store. -func (s *DirectStore) Layouts(ctx context.Context) chronograf.LayoutsStore { - return s.LayoutsStore -} - -// Users returns a chronograf.UsersStore. -// If the context is a server context, then the underlying chronograf.UsersStore -// is returned. -// If there is an organization specified on context, then an organizations.UsersStore -// is returned. -// If neither are specified, a noop.UsersStore is returned. -func (s *DirectStore) Users(ctx context.Context) chronograf.UsersStore { - return s.UsersStore -} - -// Dashboards returns a noop.DashboardsStore if the context has no organization specified -// and an organization.DashboardsStore otherwise. -func (s *DirectStore) Dashboards(ctx context.Context) chronograf.DashboardsStore { - return s.DashboardsStore -} - -// OrganizationConfig returns a noop.OrganizationConfigStore if the context has no organization specified -// and an organization.OrganizationConfigStore otherwise. -func (s *DirectStore) OrganizationConfig(ctx context.Context) chronograf.OrganizationConfigStore { - return s.OrganizationConfigStore -} - -// Organizations returns the underlying OrganizationsStore. -func (s *DirectStore) Organizations(ctx context.Context) chronograf.OrganizationsStore { - return s.OrganizationsStore -} - -// Config returns the underlying ConfigStore. -func (s *DirectStore) Config(ctx context.Context) chronograf.ConfigStore { - return s.ConfigStore -} - -// Mappings returns the underlying MappingsStore. -func (s *DirectStore) Mappings(ctx context.Context) chronograf.MappingsStore { - return s.MappingsStore -} diff --git a/chronograf/server/stores_test.go b/chronograf/server/stores_test.go deleted file mode 100644 index 5882c786d62..00000000000 --- a/chronograf/server/stores_test.go +++ /dev/null @@ -1,428 +0,0 @@ -package server - -import ( - "context" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/organizations" -) - -func TestStore_SourcesGet(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - } - type args struct { - organization string - id int - } - type wants struct { - source chronograf.Source - err bool - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "Get source", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - }, nil - }, - }, - }, - args: args{ - organization: "0", - }, - wants: wants{ - source: chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - }, - }, - }, - { - name: "Get source - no organization specified on context", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - GetF: func(ctx context.Context, id int) (chronograf.Source, error) { - return chronograf.Source{ - ID: 1, - Name: "my sweet name", - Organization: "0", - }, nil - }, - }, - }, - args: args{}, - wants: wants{ - err: true, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - store := &Store{ - SourcesStore: tt.fields.SourcesStore, - } - - ctx := context.Background() - - if tt.args.organization != "" { - ctx = context.WithValue(ctx, organizations.ContextKey, tt.args.organization) - } - - source, err := store.Sources(ctx).Get(ctx, tt.args.id) - if (err != nil) != tt.wants.err { - t.Errorf("%q. Store.Sources().Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) - return - } - if diff := cmp.Diff(source, tt.wants.source); diff != "" { - t.Errorf("%q. Store.Sources().Get():\n-got/+want\ndiff %s", tt.name, diff) - } - }) - } -} - -func TestStore_SourcesAll(t *testing.T) { - type fields struct { - SourcesStore chronograf.SourcesStore - } - type args struct { - organization string - } - type wants struct { - sources []chronograf.Source - err bool - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "Get sources", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - }, - }, nil - }, - }, - }, - args: args{ - organization: "0", - }, - wants: wants{ - sources: []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - }, - }, - }, - }, - { - name: "Get sources - multiple orgs", - fields: fields{ - SourcesStore: &mocks.SourcesStore{ - AllF: func(ctx context.Context) ([]chronograf.Source, error) { - return []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - }, - { - ID: 2, - Name: "A bad source", - Organization: "0", - }, - { - ID: 3, - Name: "A good source", - Organization: "0", - }, - { - ID: 4, - Name: "a source I can has", - Organization: "0", - }, - { - ID: 5, - Name: "i'm in the wrong org", - Organization: "1", - }, - }, nil - }, - }, - }, - args: args{ - organization: "0", - }, - wants: wants{ - sources: []chronograf.Source{ - { - ID: 1, - Name: "my sweet name", - Organization: "0", - }, - { - ID: 2, - Name: "A bad source", - Organization: "0", - }, - { - ID: 3, - Name: "A good source", - Organization: "0", - }, - { - ID: 4, - Name: "a source I can has", - Organization: "0", - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - store := &Store{ - SourcesStore: tt.fields.SourcesStore, - } - - ctx := context.Background() - - if tt.args.organization != "" { - ctx = context.WithValue(ctx, organizations.ContextKey, tt.args.organization) - } - - sources, err := store.Sources(ctx).All(ctx) - if (err != nil) != tt.wants.err { - t.Errorf("%q. Store.Sources().Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) - return - } - if diff := cmp.Diff(sources, tt.wants.sources); diff != "" { - t.Errorf("%q. Store.Sources().Get():\n-got/+want\ndiff %s", tt.name, diff) - } - }) - } -} - -func TestStore_OrganizationsAdd(t *testing.T) { - type fields struct { - OrganizationsStore chronograf.OrganizationsStore - } - type args struct { - orgID string - serverContext bool - organization string - user *chronograf.User - } - type wants struct { - organization *chronograf.Organization - err bool - } - - tests := []struct { - name string - fields fields - args args - wants wants - }{ - { - name: "Get organization with server context", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "21", - Name: "my sweet name", - DefaultRole: "viewer", - }, nil - }, - }, - }, - args: args{ - serverContext: true, - orgID: "21", - }, - wants: wants{ - organization: &chronograf.Organization{ - ID: "21", - Name: "my sweet name", - DefaultRole: "viewer", - }, - }, - }, - { - name: "Get organization with super admin", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "21", - Name: "my sweet name", - DefaultRole: "viewer", - }, nil - }, - }, - }, - args: args{ - user: &chronograf.User{ - ID: 1337, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - }, - orgID: "21", - }, - wants: wants{ - organization: &chronograf.Organization{ - ID: "21", - Name: "my sweet name", - DefaultRole: "viewer", - }, - }, - }, - { - name: "Get organization not as super admin no organization", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "21", - Name: "my sweet name", - DefaultRole: "viewer", - }, nil - }, - }, - }, - args: args{ - user: &chronograf.User{ - ID: 1337, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - }, - orgID: "21", - }, - wants: wants{ - err: true, - }, - }, - { - name: "Get organization not as super admin with organization", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "22", - Name: "my sweet name", - DefaultRole: "viewer", - }, nil - }, - }, - }, - args: args{ - user: &chronograf.User{ - ID: 1337, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - }, - organization: "22", - orgID: "22", - }, - wants: wants{ - organization: &chronograf.Organization{ - ID: "22", - Name: "my sweet name", - DefaultRole: "viewer", - }, - }, - }, - { - name: "Get different organization not as super admin with organization", - fields: fields{ - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - return &chronograf.Organization{ - ID: "22", - Name: "my sweet name", - DefaultRole: "viewer", - }, nil - }, - }, - }, - args: args{ - user: &chronograf.User{ - ID: 1337, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - }, - organization: "21", - orgID: "21", - }, - wants: wants{ - err: true, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - store := &Store{ - OrganizationsStore: tt.fields.OrganizationsStore, - } - - ctx := context.Background() - - if tt.args.serverContext { - ctx = serverContext(ctx) - } - - if tt.args.organization != "" { - ctx = context.WithValue(ctx, organizations.ContextKey, tt.args.organization) - } - - if tt.args.user != nil { - ctx = context.WithValue(ctx, UserContextKey, tt.args.user) - } - - organization, err := store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &tt.args.orgID}) - if (err != nil) != tt.wants.err { - t.Errorf("%q. Store.Organizations().Get() error = %v, wantErr %v", tt.name, err, tt.wants.err) - return - } - if diff := cmp.Diff(organization, tt.wants.organization); diff != "" { - t.Errorf("%q. Store.Organizations().Get():\n-got/+want\ndiff %s", tt.name, diff) - } - }) - } -} diff --git a/chronograf/server/swagger.go b/chronograf/server/swagger.go deleted file mode 100644 index 83f97ceee19..00000000000 --- a/chronograf/server/swagger.go +++ /dev/null @@ -1,20 +0,0 @@ -package server - -//go:generate env GO111MODULE=on go run github.com/kevinburke/go-bindata/go-bindata -o swagger_gen.go -tags assets -ignore go -nocompress -pkg server . - -import "net/http" - -// Spec servers the swagger.json file from bindata -func Spec() http.HandlerFunc { - swagger, err := Asset("swagger.json") - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write(swagger) - }) -} diff --git a/chronograf/server/swagger.json b/chronograf/server/swagger.json deleted file mode 100644 index db58b912b34..00000000000 --- a/chronograf/server/swagger.json +++ /dev/null @@ -1,6080 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "title": "Chronograf", - "description": "API endpoints for Chronograf", - "version": "1.5.0.0" - }, - "schemes": ["http"], - "basePath": "/chronograf/v1", - "consumes": ["application/json"], - "produces": ["application/json"], - "paths": { - "/": { - "get": { - "tags": ["routes"], - "summary": "Lists all the endpoints", - "description": "List of the endpoints.", - "responses": { - "200": { - "description": "Returns the links to the top level endpoints.", - "schema": { - "$ref": "#/definitions/Routes" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources": { - "get": { - "tags": ["sources"], - "summary": "Configured data sources", - "description": "These data sources store time series data.", - "responses": { - "200": { - "description": "An array of data sources", - "schema": { - "$ref": "#/definitions/Sources" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["sources"], - "summary": "Create new data source", - "parameters": [ - { - "name": "source", - "in": "body", - "description": "Configuration options for data source", - "schema": { - "$ref": "#/definitions/Source" - } - } - ], - "responses": { - "201": { - "description": "Data source successfully created", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": - "Location of the newly created data source resource." - } - }, - "schema": { - "$ref": "#/definitions/Source" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}": { - "get": { - "tags": ["sources"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - } - ], - "summary": "Configured data sources", - "description": "These data sources store time series data.", - "responses": { - "200": { - "description": - "Data source used to supply time series information.", - "schema": { - "$ref": "#/definitions/Source" - } - }, - "404": { - "description": "Unknown source id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "patch": { - "tags": ["sources"], - "summary": "Update data source configuration", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of a data source", - "required": true - }, - { - "name": "config", - "in": "body", - "description": "data source configuration", - "schema": { - "$ref": "#/definitions/Source" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Data source's configuration was changed", - "schema": { - "$ref": "#/definitions/Source" - } - }, - "404": { - "description": - "Happens when trying to access a non-existent data source.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["sources"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - } - ], - "summary": - "This specific data source will be removed from the data store. All associated kapacitor resources and kapacitor rules resources are also removed.", - "responses": { - "204": { - "description": "data source has been removed" - }, - "404": { - "description": "Unknown data source id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/queries": { - "post": { - "tags": ["sources", "queries"], - "description": "Used to analyze queries for structure`", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "queries", - "in": "body", - "description": "Query Parameters", - "schema": { - "$ref": "#/definitions/Queries" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Result of the analysis of the query.", - "schema": { - "$ref": "#/definitions/QueriesResponse" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/proxy": { - "post": { - "tags": ["sources", "proxy"], - "description": - "Query the backend time series data source and return the response according to `format`", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "query", - "in": "body", - "description": "Query Parameters", - "schema": { - "$ref": "#/definitions/Proxy" - }, - "required": true - } - ], - "responses": { - "200": { - "description": - "Result of the query from the backend time series data source.", - "schema": { - "$ref": "#/definitions/ProxyResponse" - } - }, - "400": { - "description": - "Any query that results in a data source error (syntax error, etc) will cause this response. The error message will be passed back in the body", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "408": { - "description": "Timeout trying to query data source.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/write": { - "post": { - "tags": ["sources", "write"], - "description": "Write points to the backend time series data source", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "query", - "in": "body", - "description": "Write Parameters", - "schema": { - "type": "string", - "format": "byte" - }, - "required": true - }, - { - "name": "db", - "in": "query", - "description": "Sets the target database for the write.", - "type": "string", - "required": true - }, - { - "name": "rp", - "in": "query", - "description": - "Sets the target retention policy for the write. InfluxDB writes to the DEFAULT retention policy if you do not specify a retention policy.", - "type": "string" - }, - { - "name": "precision", - "in": "query", - "description": - "Sets the precision for the supplied Unix time values. InfluxDB assumes that timestamps are in nanoseconds if you do not specify precision.", - "type": "string", - "enum": ["ns", "u", "ms", "s", "m", "h"] - }, - { - "name": "consistency", - "in": "query", - "description": - "Sets the write consistency for the point. InfluxDB assumes that the write consistency is one if you do not specify consistency. See the InfluxEnterprise documentation for detailed descriptions of each consistency option.", - "type": "string", - "enum": ["any", "one", "quorum", "all"] - } - ], - "responses": { - "204": { - "description": "Points written successfuly to database." - }, - "400": { - "description": - "Any query that results in a data source error (syntax error, etc) will cause this response. The error message will be passed back in the body", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "408": { - "description": "Timeout trying to query data source.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/health": { - "get": { - "tags": ["sources"], - "summary": "Health check for source", - "description": "Returns if the tsdb source can be contacted", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - } - ], - "responses": { - "204": { - "description": "Source was able to be contacted" - }, - "404": { - "description": "Source could not be contacted", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/permissions": { - "get": { - "tags": ["sources", "users"], - "summary": "Retrieve possible permissions for this data source", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - } - ], - "responses": { - "200": { - "description": "Listing of all possible permissions", - "schema": { - "$ref": "#/definitions/AllPermissions" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/users": { - "get": { - "tags": ["sources", "users"], - "summary": "Retrieve all data sources users", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - } - ], - "responses": { - "200": { - "description": "Listing of all users", - "schema": { - "$ref": "#/definitions/InfluxDB-Users" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["sources", "users"], - "summary": "Create new user for this data source", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "user", - "in": "body", - "description": "Configuration options for new user", - "schema": { - "$ref": "#/definitions/InfluxDB-User" - } - } - ], - "responses": { - "201": { - "description": "User successfully created", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": "Location of the newly created user resource." - } - }, - "schema": { - "$ref": "#/definitions/InfluxDB-User" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/users/{user_id}": { - "get": { - "tags": ["sources", "users"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "user_id", - "in": "path", - "type": "string", - "description": "ID of the specific user", - "required": true - } - ], - "summary": "Returns information about a specific user", - "description": "Specific User within a data source", - "responses": { - "200": { - "description": "Information relating to the user", - "schema": { - "$ref": "#/definitions/InfluxDB-User" - } - }, - "404": { - "description": "Unknown user or unknown source", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "patch": { - "tags": ["sources", "users"], - "summary": "Update user configuration", - "description": - "Update one parameter at a time (one of password, permissions or roles)", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "user_id", - "in": "path", - "type": "string", - "description": "ID of the specific user", - "required": true - }, - { - "name": "config", - "in": "body", - "description": "user configuration", - "schema": { - "$ref": "#/definitions/InfluxDB-User" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Users's configuration was changed", - "schema": { - "$ref": "#/definitions/InfluxDB-User" - } - }, - "404": { - "description": - "Happens when trying to access a non-existent user or source.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["sources", "users"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "user_id", - "in": "path", - "type": "string", - "description": "ID of the specific user", - "required": true - } - ], - "summary": "This specific user will be removed from the data source", - "responses": { - "204": { - "description": "User has been removed" - }, - "404": { - "description": "Unknown user id or data source", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/roles": { - "get": { - "tags": ["sources", "users", "roles"], - "summary": - "Retrieve all data sources roles. Available only in Influx Enterprise", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - } - ], - "responses": { - "200": { - "description": "Listing of all roles", - "schema": { - "$ref": "#/definitions/InfluxDB-Roles" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["sources", "users", "roles"], - "summary": "Create new role for this data source", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "roleuser", - "in": "body", - "description": "Configuration options for new role", - "schema": { - "$ref": "#/definitions/InfluxDB-Role" - } - } - ], - "responses": { - "201": { - "description": "Role successfully created", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": "Location of the newly created role resource." - } - }, - "schema": { - "$ref": "#/definitions/InfluxDB-Role" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/roles/{role_id}": { - "get": { - "tags": ["sources", "users", "roles"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "role_id", - "in": "path", - "type": "string", - "description": "ID of the specific role", - "required": true - } - ], - "summary": "Returns information about a specific role", - "description": "Specific role within a data source", - "responses": { - "200": { - "description": "Information relating to the role", - "schema": { - "$ref": "#/definitions/InfluxDB-Role" - } - }, - "404": { - "description": "Unknown role or unknown source", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "patch": { - "tags": ["sources", "users", "roles"], - "summary": "Update role configuration", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "role_id", - "in": "path", - "type": "string", - "description": "ID of the specific role", - "required": true - }, - { - "name": "config", - "in": "body", - "description": "role configuration", - "schema": { - "$ref": "#/definitions/InfluxDB-Role" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Roles's configuration was changed", - "schema": { - "$ref": "#/definitions/InfluxDB-Role" - } - }, - "404": { - "description": - "Happens when trying to access a non-existent role or source.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["sources", "users", "roles"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "role_id", - "in": "path", - "type": "string", - "description": "ID of the specific role", - "required": true - } - ], - "summary": "This specific role will be removed from the data source", - "responses": { - "204": { - "description": "Role has been removed" - }, - "404": { - "description": "Unknown role id or data source", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/dbs/": { - "get": { - "tags": ["databases"], - "summary": "Retrieve all databases for a source", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - } - ], - "responses": { - "200": { - "description": "Listing of all databases for a source", - "schema": { - "$ref": "#/definitions/Databases" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["databases"], - "summary": "Create new database for a source", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "database", - "in": "body", - "description": "Configuration options for a database", - "schema": { - "$ref": "#/definitions/Database" - }, - "required": true - } - ], - "responses": { - "201": { - "description": "Database successfully created.", - "schema": { - "$ref": "#/definitions/Database" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/dbs/{db}": { - "delete": { - "tags": ["databases"], - "summary": "Delete database for a source", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "db", - "in": "path", - "type": "string", - "description": "Name of the database", - "required": true - } - ], - "responses": { - "204": { - "description": "Database has been deleted" - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/dbs/{db}/rps": { - "get": { - "tags": ["retention policies"], - "summary": "Retrieve all retention policies for a database", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "db", - "in": "path", - "type": "string", - "description": "Name of the database", - "required": true - } - ], - "responses": { - "200": { - "description": "Listing of all retention policies for a database", - "schema": { - "$ref": "#/definitions/RetentionPolicies" - } - }, - "404": { - "description": "Specified retention policy does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["retention policies"], - "summary": "Create new retention policy for a database", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "db", - "in": "path", - "type": "string", - "description": "Name of the database", - "required": true - }, - { - "name": "rp", - "in": "body", - "description": "Configuration options for the retention policy", - "schema": { - "$ref": "#/definitions/RetentionPolicy" - }, - "required": true - } - ], - "responses": { - "201": { - "description": "Retention Policy successfully created.", - "schema": { - "$ref": "#/definitions/RetentionPolicy" - } - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/dbs/{db}/rps/{rp}": { - "patch": { - "tags": ["retention policies"], - "summary": "Alter retention policy for a database", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "db", - "in": "path", - "type": "string", - "description": "Name of the database", - "required": true - }, - { - "name": "rp", - "in": "path", - "type": "string", - "description": "Name of the retention policy", - "required": true - }, - { - "name": "rp", - "in": "body", - "description": "Configuration options for the retention policy", - "schema": { - "$ref": "#/definitions/RetentionPolicy" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Retention Policy was altered", - "schema": { - "$ref": "#/definitions/RetentionPolicy" - } - }, - "404": { - "description": "Database or source does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["retention policies"], - "summary": "Delete retention policy for a database", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "name": "db", - "in": "path", - "type": "string", - "description": "Name of the database", - "required": true - }, - { - "name": "rp", - "in": "path", - "type": "string", - "description": "Name of the retention policy", - "required": true - } - ], - "responses": { - "204": { - "description": "Retention Policy has been deleted" - }, - "404": { - "description": "Data source id does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal service error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/dbs/{db}/measurements": { - "get": { - "tags": ["measurements"], - "summary": "Retrieve measurements in a database", - "parameters": [ - { - "in": "path", - "name": "id", - "type": "string", - "description": "ID of the data source", - "required": true - }, - { - "in": "path", - "name": "db", - "type": "string", - "description": "Name of the database", - "required": true - }, - { - "in": "query", - "name": "limit", - "type": "integer", - "minimum": 1, - "default": 100, - "description": - "The upper limit of the number of available database measurements to return.", - "required": false - }, - { - "in": "query", - "name": "offset", - "type": "integer", - "minimum": 0, - "default": 0, - "description": - "The number of measurements to skip before starting to collect the result set.", - "required": false - } - ], - "responses": { - "200": { - "description": "Listing of measurements for a database", - "schema": { - "$ref": "#/definitions/MeasurementsResponse" - } - }, - "400": { - "description": - "Unable to connect to source; or unable to get measurements from database.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Source not found.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "422": { - "description": - "Invalid source id param value in path; or invalid limit or offset param value in query.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal service error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/kapacitors": { - "get": { - "tags": ["sources", "kapacitors"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - } - ], - "summary": "Retrieve list of configured kapacitors", - "responses": { - "200": { - "description": "An array of kapacitors", - "schema": { - "$ref": "#/definitions/Kapacitors" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["sources", "kapacitors"], - "summary": "Create new kapacitor backend", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapacitor", - "in": "body", - "description": "Configuration options for kapacitor", - "schema": { - "$ref": "#/definitions/Kapacitor" - } - } - ], - "responses": { - "201": { - "description": "Kapacitor source successfully created", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": - "Location of the newly created kapacitor resource." - } - }, - "schema": { - "$ref": "#/definitions/Kapacitor" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/kapacitors/{kapa_id}": { - "get": { - "tags": ["sources", "kapacitors"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor", - "required": true - } - ], - "summary": "Configured kapacitors", - "description": "Retrieve information on a single kapacitor instance", - "responses": { - "200": { - "description": "Kapacitor connection information", - "schema": { - "$ref": "#/definitions/Kapacitor" - } - }, - "404": { - "description": "Unknown data source or kapacitor id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "patch": { - "tags": ["sources", "kapacitors"], - "summary": "Update kapacitor configuration", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of a kapacitor backend", - "required": true - }, - { - "name": "config", - "in": "body", - "description": "kapacitor configuration", - "schema": { - "$ref": "#/definitions/Kapacitor" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Kapacitor's configuration was changed", - "schema": { - "$ref": "#/definitions/Kapacitor" - } - }, - "404": { - "description": - "Happens when trying to access a non-existent data source or kapacitor.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["sources", "kapacitors"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor", - "required": true - } - ], - "summary": "Remove Kapacitor backend", - "description": - "This specific kapacitor will be removed. All associated rule resources will also be removed from the store.", - "responses": { - "204": { - "description": "kapacitor has been removed." - }, - "404": { - "description": "Unknown Data source or Kapacitor id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/kapacitors/{kapa_id}/rules": { - "get": { - "tags": ["sources", "kapacitors", "rules"], - "description": "Get all defined alert rules.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - } - ], - "responses": { - "200": { - "description": - "All alert rules for this specific kapacitor are returned", - "schema": { - "$ref": "#/definitions/Rules" - } - }, - "404": { - "description": "Data source or Kapacitor ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["sources", "kapacitors", "rules"], - "description": "Create kapacitor alert rule", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - }, - { - "name": "rule", - "in": "body", - "description": "Rule to generate alert rule", - "schema": { - "$ref": "#/definitions/Rule" - }, - "required": true - } - ], - "responses": { - "201": { - "description": "Kapacitor alert rule successfully created", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": - "Location of the newly created kapacitor rule resource." - } - }, - "schema": { - "$ref": "#/definitions/Rule" - } - }, - "404": { - "description": "Source ID or Kapacitor ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "422": { - "description": - "Source ID , Kapacitor ID or alert are unprocessable", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": - "Internal server error; generally a problem creating alert in kapacitor", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/kapacitors/{kapa_id}/rules/{rule_id}": { - "get": { - "tags": ["sources", "kapacitors", "rules"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor", - "required": true - }, - { - "name": "rule_id", - "in": "path", - "type": "string", - "description": "ID of the rule", - "required": true - } - ], - "summary": "Specific kapacitor alert rule", - "description": "Alerting rule for kapacitor", - "responses": { - "200": { - "description": "Alert exists and has a specific TICKscript", - "schema": { - "$ref": "#/definitions/Rule" - } - }, - "404": { - "description": "Unknown data source, kapacitor id, or rule id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "put": { - "tags": ["sources", "kapacitors", "rules"], - "summary": "Update rule alert rule configuration", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of a kapacitor backend", - "required": true - }, - { - "name": "rule_id", - "in": "path", - "type": "string", - "description": "ID of a rule", - "required": true - }, - { - "name": "rule", - "in": "body", - "description": "Rule update", - "schema": { - "$ref": "#/definitions/Rule" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Alert configuration was changed", - "schema": { - "$ref": "#/definitions/Rule" - } - }, - "404": { - "description": - "Happens when trying to access a non-existent data source, kapacitor, or rule.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["sources", "kapacitors", "rules"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor", - "required": true - }, - { - "name": "rule_id", - "in": "path", - "type": "string", - "description": "ID of the rule", - "required": true - } - ], - "summary": "This specific alert rule will be removed.", - "responses": { - "204": { - "description": "Alert rule has been removed." - }, - "404": { - "description": "Unknown Data source, Kapacitor id, or alert rule", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/kapacitors/{kapa_id}/proxy": { - "get": { - "tags": ["sources", "kapacitors", "proxy"], - "description": - "GET to `path` of kapacitor. The response and status code from kapacitor is directly returned.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - }, - { - "name": "path", - "in": "query", - "type": "string", - "description": - "The kapacitor API path to use in the proxy redirect", - "required": true - } - ], - "responses": { - "204": { - "description": "Kapacitor returned no content" - }, - "404": { - "description": "Data source or Kapacitor ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Response directly from kapacitor", - "schema": { - "$ref": "#/definitions/KapacitorProxyResponse" - } - } - } - }, - "delete": { - "tags": ["sources", "kapacitors", "proxy"], - "description": - "DELETE to `path` of kapacitor. The response and status code from kapacitor is directly returned.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - }, - { - "name": "path", - "in": "query", - "type": "string", - "description": - "The kapacitor API path to use in the proxy redirect", - "required": true - } - ], - "responses": { - "204": { - "description": "Kapacitor returned no content" - }, - "404": { - "description": "Data source or Kapacitor ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Response directly from kapacitor", - "schema": { - "$ref": "#/definitions/KapacitorProxyResponse" - } - } - } - }, - "patch": { - "tags": ["sources", "kapacitors", "proxy"], - "description": - "PATCH body directly to configured kapacitor. The response and status code from kapacitor is directly returned.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - }, - { - "name": "path", - "in": "query", - "type": "string", - "description": - "The kapacitor API path to use in the proxy redirect", - "required": true - }, - { - "name": "query", - "in": "body", - "description": "Kapacitor body", - "schema": { - "$ref": "#/definitions/KapacitorProxy" - }, - "required": true - } - ], - "responses": { - "204": { - "description": "Kapacitor returned no content" - }, - "404": { - "description": "Data source or Kapacitor ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Response directly from kapacitor", - "schema": { - "$ref": "#/definitions/KapacitorProxyResponse" - } - } - } - }, - "post": { - "tags": ["sources", "kapacitors", "proxy"], - "description": - "POST body directly to configured kapacitor. The response and status code from kapacitor is directly returned.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "kapa_id", - "in": "path", - "type": "string", - "description": "ID of the kapacitor backend.", - "required": true - }, - { - "name": "path", - "in": "query", - "type": "string", - "description": - "The kapacitor API path to use in the proxy redirect", - "required": true - }, - { - "name": "query", - "in": "body", - "description": "Kapacitor body", - "schema": { - "$ref": "#/definitions/KapacitorProxy" - }, - "required": true - } - ], - "responses": { - "204": { - "description": "Kapacitor returned no content" - }, - "404": { - "description": "Kapacitor ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Response directly from kapacitor", - "schema": { - "$ref": "#/definitions/KapacitorProxyResponse" - } - } - } - } - }, - "/sources/{id}/services": { - "get": { - "tags": ["sources", "services"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - } - ], - "summary": "Retrieve list of services for a source", - "responses": { - "200": { - "description": "An array of services", - "schema": { - "$ref": "#/definitions/Services" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["sources", "services"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "service", - "in": "body", - "description": "Configuration options for the service", - "schema": { - "$ref": "#/definitions/Service" - } - } - ], - "summary": "Create a new service", - "responses": { - "200": { - "description": "Returns the newly created service", - "schema": { - "$ref": "#/definitions/Service" - } - }, - "504": { - "description": "Gateway timeout happens when the server cannot connect to the service", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/services/{srv_id}": { - "get": { - "tags": ["sources", "services"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "srv_id", - "in": "path", - "type": "string", - "description": "ID of the service", - "required": true - } - ], - "summary": "Retrieve a service", - "description": "Retrieve a single service by id", - "responses": { - "200": { - "description": "Service connection information", - "schema": { - "$ref": "#/definitions/Service" - } - }, - "404": { - "description": "Unknown data source or service id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "patch": { - "tags": ["sources", "services"], - "summary": "Update service configuration", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "srv_id", - "in": "path", - "type": "string", - "description": "ID of a service backend", - "required": true - }, - { - "name": "service", - "in": "body", - "description": "service configuration", - "schema": { - "$ref": "#/definitions/Service" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Service configuration was changed", - "schema": { - "$ref": "#/definitions/Service" - } - }, - "504": { - "description": "Gateway timeout happens when the server cannot connect to the service", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "422": { - "description": "Unprocessable entity happens when the service ID provided does not exist", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["sources", "services"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "srv_id", - "in": "path", - "type": "string", - "description": "ID of the service", - "required": true - } - ], - "summary": "Remove Service backend", - "description": - "This specific service will be removed.", - "responses": { - "204": { - "description": "service has been removed." - }, - "404": { - "description": "Unknown Data source or Service id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/sources/{id}/services/{srv_id}/proxy": { - "get": { - "tags": ["sources", "services", "proxy"], - "description": - "GET to `path` of Service. The response and status code from Service is directly returned.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "srv_id", - "in": "path", - "type": "string", - "description": "ID of the service backend.", - "required": true - }, - { - "name": "path", - "in": "query", - "type": "string", - "description": - "The Service API path to use in the proxy redirect", - "required": true - } - ], - "responses": { - "204": { - "description": "Service returned no content" - }, - "404": { - "description": "Data source or Service ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Response directly from the service", - "schema": { - "$ref": "#/definitions/ServiceProxyResponse" - } - } - } - }, - "delete": { - "tags": ["sources", "services", "proxy"], - "description": - "DELETE to `path` of Service. The response and status code from the service is directly returned.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "srv_id", - "in": "path", - "type": "string", - "description": "ID of the Service backend.", - "required": true - }, - { - "name": "path", - "in": "query", - "type": "string", - "description": - "The Service API path to use in the proxy redirect", - "required": true - } - ], - "responses": { - "204": { - "description": "Service returned no content" - }, - "404": { - "description": "Data source or Service ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Response directly from the service", - "schema": { - "$ref": "#/definitions/ServiceProxyResponse" - } - } - } - }, - "patch": { - "tags": ["sources", "services", "proxy"], - "description": - "PATCH body directly to configured service. The response and status code from Service is directly returned.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "srv_id", - "in": "path", - "type": "string", - "description": "ID of the Service backend.", - "required": true - }, - { - "name": "path", - "in": "query", - "type": "string", - "description": - "The Service API path to use in the proxy redirect", - "required": true - }, - { - "name": "query", - "in": "body", - "description": "Service body", - "schema": { - "$ref": "#/definitions/ServiceProxy" - }, - "required": true - } - ], - "responses": { - "204": { - "description": "Service returned no content" - }, - "404": { - "description": "Data source or Service ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Response directly from Service", - "schema": { - "$ref": "#/definitions/ServiceProxyResponse" - } - } - } - }, - "post": { - "tags": ["sources", "services", "proxy"], - "description": - "POST body directly to configured Service. The response and status code from Service is directly returned.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the source", - "required": true - }, - { - "name": "srv_id", - "in": "path", - "type": "string", - "description": "ID of the Service backend.", - "required": true - }, - { - "name": "path", - "in": "query", - "type": "string", - "description": - "The Service API path to use in the proxy redirect", - "required": true - }, - { - "name": "query", - "in": "body", - "description": "Service body", - "schema": { - "$ref": "#/definitions/ServiceProxy" - }, - "required": true - } - ], - "responses": { - "204": { - "description": "Service returned no content" - }, - "404": { - "description": "Service ID does not exist.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Response directly from Service", - "schema": { - "$ref": "#/definitions/ServiceProxyResponse" - } - } - } - } - }, - "/mappings": { - "get": { - "tags": ["layouts", "mappings"], - "summary": "Mappings between app names and measurements", - "description": - "Mappings provide a means to alias measurement names found within a telegraf database and application layouts found within Chronograf\n", - "responses": { - "200": { - "description": "An array of mappings", - "schema": { - "$ref": "#/definitions/Mappings" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/layouts": { - "get": { - "tags": ["layouts"], - "summary": "Pre-configured layouts", - "parameters": [ - { - "name": "measurement", - "in": "query", - "description": "Returns layouts with this measurement", - "required": false, - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "multi" - }, - { - "name": "app", - "in": "query", - "description": "Returns layouts with this app", - "required": false, - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "multi" - } - ], - "description": - "Layouts are a collection of `Cells` that visualize time-series data.\n", - "responses": { - "200": { - "description": "An array of layouts", - "schema": { - "$ref": "#/definitions/Layouts" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["layouts"], - "summary": "Create new layout", - "parameters": [ - { - "name": "layout", - "in": "body", - "description": - "Defines the layout and queries of the cells within the layout.", - "schema": { - "$ref": "#/definitions/Layout" - } - } - ], - "responses": { - "201": { - "description": "Layout successfully created", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": "Location of the newly created layout" - } - }, - "schema": { - "$ref": "#/definitions/Layout" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/layouts/{id}": { - "get": { - "tags": ["layouts"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the layout", - "required": true - } - ], - "summary": - "Specific pre-configured layout containing cells and queries.", - "description": - "layouts will hold information about how to layout the page of graphs.\n", - "responses": { - "200": { - "description": "Returns the specified layout containing `cells`.", - "schema": { - "$ref": "#/definitions/Layout" - } - }, - "404": { - "description": "Unknown layout id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["layouts"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the layout", - "required": true - } - ], - "summary": "This specific layout will be removed from the data store", - "responses": { - "204": { - "description": "Layout has been removed." - }, - "404": { - "description": "Unknown layout id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "put": { - "tags": ["layouts"], - "summary": "Replace layout configuration.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of a layout", - "required": true - }, - { - "name": "config", - "in": "body", - "description": "layout configuration update parameters", - "schema": { - "$ref": "#/definitions/Layout" - }, - "required": true - } - ], - "responses": { - "200": { - "description": - "Layout has been replaced and the new layout is returned.", - "schema": { - "$ref": "#/definitions/Layout" - } - }, - "404": { - "description": - "Happens when trying to access a non-existent layout.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/dashboards": { - "get": { - "tags": ["dashboards"], - "summary": "List of all dashboards", - "responses": { - "200": { - "description": "An array of dashboards", - "schema": { - "$ref": "#/definitions/Dashboards" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["dashboards"], - "summary": "Create new dashboard", - "parameters": [ - { - "name": "dashboard", - "in": "body", - "description": "Configuration options for new dashboard", - "schema": { - "$ref": "#/definitions/Dashboard" - } - } - ], - "responses": { - "201": { - "description": "Dashboard successfully created", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": - "Location of the newly created dashboard resource." - } - }, - "schema": { - "$ref": "#/definitions/Dashboard" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/dashboards/{id}": { - "get": { - "tags": ["dashboards"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "integer", - "description": "ID of the dashboard", - "required": true - } - ], - "summary": "Specific dashboard", - "description": - "Dashboards contain visual display information as well as links to queries", - "responses": { - "200": { - "description": - "Returns the specified dashboard with links to queries.", - "schema": { - "$ref": "#/definitions/Dashboard" - } - }, - "404": { - "description": "Unknown dashboard id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["dashboards"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "integer", - "description": "ID of the layout", - "required": true - } - ], - "summary": "Deletes the specified dashboard", - "responses": { - "204": { - "description": "Dashboard has been removed." - }, - "404": { - "description": "Unknown dashboard id", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "put": { - "tags": ["dashboards"], - "summary": "Replace dashboard information.", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "integer", - "description": "ID of a dashboard", - "required": true - }, - { - "name": "config", - "in": "body", - "description": "dashboard configuration update parameters", - "schema": { - "$ref": "#/definitions/Dashboard" - }, - "required": true - } - ], - "responses": { - "200": { - "description": - "Dashboard has been replaced and the new dashboard is returned.", - "schema": { - "$ref": "#/definitions/Dashboard" - } - }, - "404": { - "description": - "Happens when trying to access a non-existent dashboard.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "patch": { - "tags": ["layouts"], - "summary": "Update dashboard information.", - "description": - "Update either the dashboard name or the dashboard cells", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "integer", - "description": "ID of a dashboard", - "required": true - }, - { - "name": "config", - "in": "body", - "description": - "dashboard configuration update parameters. Must be either name or cells", - "schema": { - "$ref": "#/definitions/Dashboard" - }, - "required": true - } - ], - "responses": { - "200": { - "description": - "Dashboard has been updated and the new dashboard is returned.", - "schema": { - "$ref": "#/definitions/Dashboard" - } - }, - "404": { - "description": - "Happens when trying to access a non-existent dashboard.", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "A processing or an unexpected error.", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/organizations": { - "get": { - "tags": ["organizations", "users"], - "summary": "Retrieve all organizations", - "description": "Returns all organizations from the store", - "responses": { - "200": { - "description": - "Successfully retrieved all organizations from the store", - "schema": { - "$ref": "#/definitions/Organizations" - } - }, - "400": { - "description": "Failed to retrieve organizations from store", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["organizations", "users"], - "summary": "Create new organization", - "description": "Creates a Chronograf organization in the store", - "parameters": [ - { - "name": "organization", - "in": "body", - "description": "Organization to create", - "schema": { - "$ref": "#/definitions/Organization" - } - } - ], - "responses": { - "201": { - "description": "Organization successfully created", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": - "Location of the newly created organization resource" - } - }, - "schema": { - "$ref": "#/definitions/Organization" - } - }, - "400": { - "description": - "Invalid JSON – unable to encode or decode; or failed to perform operation in data store", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "422": { - "description": - "Invalid data schema provided to server for organization", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/organizations/{id}": { - "get": { - "tags": ["organizations", "users"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the organization", - "required": true - } - ], - "summary": "Retrieve a specific organization", - "description": "Returns a specific organization from the store", - "responses": { - "200": { - "description": "An Organization object", - "schema": { - "$ref": "#/definitions/Organization" - } - }, - "400": { - "description": "Failed to load organization from store", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "patch": { - "tags": ["organizations", "users"], - "summary": "Update existing organization", - "description": "Updates a Chronograf organization in the store", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the organization", - "required": true - }, - { - "name": "organization", - "in": "body", - "description": "Updated organization", - "schema": { - "$ref": "#/definitions/Organization" - }, - "required": true - } - ], - "responses": { - "201": { - "description": "Organization successfully updated", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": "Location of the updated organization resource" - } - }, - "schema": { - "$ref": "#/definitions/Organization" - } - }, - "400": { - "description": - "Invalid JSON – unable to encode or decode; or failed to perform operation in data store", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "422": { - "description": - "Invalid data schema provided to server for organization", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["organizations", "users"], - "summary": "Delete organization", - "description": "Deletes a Chronograf organization in the store", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the organization", - "required": true - } - ], - "responses": { - "204": { - "description": "Organization successfully deleted" - }, - "400": { - "description": "Failed to perform operation in data store", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "Organization not found", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/users": { - "get": { - "tags": ["organizations", "users"], - "summary": - "Retrieve all Chronograf users within the current organization", - "description": - "Returns all Chronograf users within the current organization from the store", - "responses": { - "200": { - "description": "Successfully retrieved all users from the store", - "schema": { - "$ref": "#/definitions/Users" - } - }, - "400": { - "description": "Failed to load users from store", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "post": { - "tags": ["organizations", "users"], - "summary": "Create new user", - "description": "Creates a Chronograf user in the store", - "parameters": [ - { - "name": "user", - "in": "body", - "description": "User to create", - "schema": { - "$ref": "#/definitions/User" - } - } - ], - "responses": { - "201": { - "description": "User successfully created", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": "Location of the newly created user resource" - } - }, - "schema": { - "$ref": "#/definitions/User" - } - }, - "400": { - "description": - "Invalid JSON – unable to encode or decode; or failed to perform operation in data store", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized to perform this operation", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "422": { - "description": "Invalid data schema provided to server for user", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/users/{id}": { - "get": { - "tags": ["organizations", "users"], - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the user", - "required": true - } - ], - "summary": "Retrieve a specific user", - "description": "Returns a specific user from the store", - "responses": { - "200": { - "description": "An User object", - "schema": { - "$ref": "#/definitions/User" - } - }, - "400": { - "description": - "Failed to load user from store; or failed to parse user ID as valid", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "patch": { - "tags": ["organizations", "users"], - "summary": "Update existing user", - "description": "Updates a Chronograf user in the store", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the user", - "required": true - }, - { - "name": "user", - "in": "body", - "description": "Updated user", - "schema": { - "$ref": "#/definitions/User" - }, - "required": true - } - ], - "responses": { - "201": { - "description": "User successfully updated", - "headers": { - "Location": { - "type": "string", - "format": "url", - "description": "Location of the updated user resource" - } - }, - "schema": { - "$ref": "#/definitions/User" - } - }, - "400": { - "description": - "Invalid JSON – unable to encode or decode; failed to parse user id as valid; or failed to perform operation in data store", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "401": { - "description": "Unauthorized to perform operation", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "User not found", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "422": { - "description": "Invalid data schema provided to server for user", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "delete": { - "tags": ["organizations", "users"], - "summary": "Delete user", - "description": "Deletes a Chronograf user in the store", - "parameters": [ - { - "name": "id", - "in": "path", - "type": "string", - "description": "ID of the user", - "required": true - } - ], - "responses": { - "204": { - "description": "User successfully deleted" - }, - "400": { - "description": - "Failed to parse user id as valid; failed to retrieve user from context; or failed to perform operation in data store", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "403": { - "description": "Forbidden to access this route", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "User not found", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/chronograf/v1/config": { - "get": { - "tags": ["config"], - "summary": "Returns the global application configuration", - "description": "All global application configurations", - "responses": { - "200": { - "description": "Returns an object with the global configurations", - "schema": { - "$ref": "#/definitions/Config" - } - }, - "404": { - "description": "Could not find global application config", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/chronograf/v1/config/auth": { - "get": { - "tags": ["config"], - "summary": "Returns the global application configuration for auth", - "description": "All global application configuration for auth", - "responses": { - "200": { - "description": "Returns an object with the global application configuration for auth", - "schema": { - "$ref": "#/definitions/AuthConfig" - } - }, - "404": { - "description": "Could not find auth configuration", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "put": { - "tags": ["config"], - "summary": "Updates the global application configuration for auth", - "description": "Replaces the global application configuration for auth", - "parameters": [ - { - "name": "auth", - "in": "body", - "description": - "Auth configuration update object", - "schema": { - "$ref": "#/definitions/AuthConfig" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Returns an object with the updated auth configuration", - "schema": { - "$ref": "#/definitions/AuthConfig" - } - }, - "404": { - "description": "Could not find auth configuration", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/chronograf/v1/org_config": { - "get": { - "tags": ["organization config"], - "summary": "Retrieve the organization configuration", - "description": "Organization-specific configurations such as log viewer configs", - "responses": { - "200": { - "description": "Returns an object with the organization-specific configurations", - "schema": { - "$ref": "#/definitions/OrganizationConfig" - } - }, - "404": { - "description": "Could not find organization config", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - }, - "/chronograf/v1/org_config/logviewer": { - "get": { - "tags": ["organization config"], - "summary": "Retrieve the organization-specific log viewer configurations", - "description": "Retrieve the log viewer configurations for the user's current organization", - "responses": { - "200": { - "description": "Returns an log viewer configuration object", - "schema": { - "$ref": "#/definitions/LogViewerConfig" - } - }, - "404": { - "description": "Could not find the log viewer configuration for this organization", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - }, - "put": { - "tags": ["organization config"], - "summary": "Update the log viewer configuration", - "description": "Update the log viewer configuration for a specific organization", - "parameters": [ - { - "name": "logViewer", - "in": "body", - "description": - "Log Viewer configuration update object", - "schema": { - "$ref": "#/definitions/LogViewerConfig" - }, - "required": true - } - ], - "responses": { - "200": { - "description": "Returns an object with the updated log viewer configurations", - "schema": { - "$ref": "#/definitions/LogViewerConfig" - } - }, - "404": { - "description": "Could not find log viewer configurations for the specified organization", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "default": { - "description": "Unexpected internal server error", - "schema": { - "$ref": "#/definitions/Error" - } - } - } - } - } - }, - "definitions": { - "Organization": { - "type": "object", - "description": - "A group of Chronograf users with various role-based access-control.", - "properties": { - "defaultRole": { - "description": - "The default role that new users in this organization will have.", - "type": "string", - "enum": ["member", "viewer", "editor", "admin"] - }, - "id": { - "type": "string", - "description": - "Unique identifier representing an organization resource. The Default organization will have the id 'default', and any further will start at '1' and increment.", - "readOnly": true - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - } - }, - "readOnly": true - }, - "name": { - "type": "string", - "description": "User-facing name of the organization resource." - } - }, - "required": ["name"], - "example": { - "defaultRole": "viewer", - "id": "1", - "links": { - "self": "/chronograf/v1/organizations/1" - }, - "name": "Chronogiraffes" - } - }, - "Organizations": { - "type": "object", - "required": ["organizations"], - "properties": { - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - } - }, - "readOnly": true - }, - "organizations": { - "type": "array", - "items": { - "$ref": "#/definitions/Organization" - } - } - } - }, - "User": { - "type": "object", - "description": - "A Chronograf user with role-based access-control to an organization's resources.", - "properties": { - "id": { - "type": "string", - "description": "Unique identifier representing a user resource", - "readOnly": true - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - } - }, - "readOnly": true - }, - "name": { - "type": "string", - "description": - "Username (as taken from principal given by auth provider)", - "readOnly": true - }, - "provider": { - "type": "string", - "description": "OAuth provider used to authenticate", - "readOnly": true - }, - "roles": { - "type": "array", - "items": { - "$ref": "#/definitions/Role" - } - }, - "scheme": { - "type": "string", - "description": - "Scheme used to authenticate (only OAuth2 currently supported)", - "readOnly": true - }, - "superAdmin": { - "type": "boolean", - "description": - "If user has the ability to perform CRUD operations on Organizations, across Organizations, and on other SuperAdmin users" - } - }, - "required": ["id", "name", "provider", "roles", "scheme"], - "example": { - "id": "1", - "links": { - "self": "/chronograf/v1/users/1" - }, - "name": "pineapple@cubeoctohedron.flux", - "provider": "github", - "roles": { - "name": "editor", - "organization": "SpaceTeam" - }, - "scheme": "oauth2", - "superAdmin": false - } - }, - "Users": { - "type": "object", - "required": ["users"], - "properties": { - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - } - } - }, - "users": { - "type": "array", - "items": { - "$ref": "#/definitions/User" - } - } - } - }, - "Role": { - "type": "object", - "properties": { - "name": { - "description": "A Chronograf user's role within an organization.", - "type": "string", - "enum": ["member", "viewer", "editor", "admin"] - }, - "organization": { - "type": "string", - "description": "Name of organization user belongs to" - } - } - }, - "Databases": { - "type": "object", - "required": ["databases"], - "properties": { - "databases": { - "type": "array", - "items": { - "$ref": "#/definitions/Database" - } - } - } - }, - "Database": { - "type": "object", - "required": ["name"], - "example": { - "name": "NOAA_water_database", - "duration": "3d", - "replication": 3, - "shardDuration": "3h", - "retentionPolicies": [ - { - "name": "weekly", - "duration": "7d", - "replication": 1, - "shardDuration": "7d", - "default": true, - "links": { - "self": - "/chronograf/v1/sources/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", - "measurements": - "/chronograf/v1/sources/1/dbs/NOAA_water_database/measurements?limit=100&offset=0" - } - }, - "properties": { - "name": { - "type": "string", - "description": "The identifying name of the database" - }, - "duration": { - "type": "string", - "description": "the duration of the default retention policy" - }, - "replication": { - "type": "integer", - "format": "int32", - "description": "how many copies of the data are stored in the cluster" - }, - "shardDuration": { - "type": "string", - "description": "the interval spanned by each shard group" - }, - "retentionPolicies": { - "type": "array", - "items": { - "$ref": "#/definitions/RetentionPolicy" - } - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - }, - "rps": { - "type": "string", - "description": "Link to retention policies for this database", - "format": "url" - }, - "measurements": { - "type": "string", - "description": "Link to measurements for this database", - "format": "url" - } - } - } - } - }, - "Kapacitors": { - "type": "object", - "required": ["kapacitors"], - "properties": { - "kapacitors": { - "type": "array", - "items": { - "$ref": "#/definitions/Kapacitor" - } - } - } - }, - "Kapacitor": { - "type": "object", - "required": ["name", "url"], - "example": { - "id": "4", - "name": "kapa", - "url": "http://localhost:9092", - "active": false, - "insecureSkipVerify": false, - "links": { - "proxy": "/chronograf/v1/sources/4/kapacitors/4/proxy", - "self": "/chronograf/v1/sources/4/kapacitors/4", - "rules": "/chronograf/v1/sources/4/kapacitors/4/rules" - } - }, - "properties": { - "id": { - "type": "string", - "description": "Unique identifier representing a kapacitor instance.", - "readOnly": true - }, - "name": { - "type": "string", - "description": "User facing name of kapacitor instance." - }, - "username": { - "type": "string", - "description": "Username for authentication to kapacitor." - }, - "password": { - "type": "string", - "description": "Password is in cleartext." - }, - "url": { - "type": "string", - "format": "url", - "description": - "URL for the kapacitor backend (e.g. http://localhost:9092)" - }, - "insecureSkipVerify": { - "type": "boolean", - "description": - "True means any certificate presented by the kapacitor is accepted. Typically used for self-signed certs. Probably should only be used for testing." - }, - "active": { - "type": "boolean", - "description": - "Indicates whether the kapacitor is the current kapacitor being used for a source" - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - }, - "proxy": { - "type": "string", - "description": - "URL location of proxy endpoint for this kapacitor", - "format": "url" - }, - "rules": { - "type": "string", - "description": - "URL location of rules endpoint for this kapacitor", - "format": "url" - } - } - } - } - }, - "KapacitorProxy": { - "description": - "Entirely used as the body for the request to the kapacitor backend.", - "type": "object" - }, - "KapacitorProxyResponse": { - "description": "Entire response from the kapacitor backend.", - "type": "object" - }, - "Services": { - "type": "object", - "required": ["services"], - "properties": { - "services": { - "type": "array", - "items": { - "$ref": "#/definitions/Service" - } - } - } - }, - "Service": { - "type": "object", - "required": ["name", "url"], - "example": { - "id": "1", - "sourceID": "1", - "url": "http://localhost:8093", - "insecureSkipVerify": false, - "type": "flux", - "metadata": { - "active": true - }, - "links": { - "proxy": "/chronograf/v1/sources/1/services/1/proxy", - "self": "/chronograf/v1/sources/1/services/1", - "source": "/chronograf/v1/sources/1" - } - }, - "properties": { - "id": { - "type": "string", - "description": "Unique identifier representing a service.", - "readOnly": true - }, - "sourceID": { - "type": "string", - "description": "Unique identifier of the source associated with this service" - }, - "name": { - "type": "string", - "description": "User facing name of the service." - }, - "username": { - "type": "string", - "description": "Credentials for using this service" - }, - "url": { - "type": "string", - "format": "url", - "description": - "URL for the service backend (e.g. http://localhost:8093)" - }, - "insecureSkipVerify": { - "type": "boolean", - "description": - "True means any certificate presented by the service is accepted. Typically used for self-signed certs. Probably should only be used for testing." - }, - "type": { - "type": "string", - "description": "Indicates what kind of service this is (e.g. flux service)" - }, - "metadata": { - "type": "object", - "properties": { - "active": { - "type": "boolean", - "description": "Indicates whether the service is the current service being used for a source" - } - } - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - }, - "proxy": { - "type": "string", - "description": - "URL location of proxy endpoint for this service", - "format": "url" - }, - "source": { - "type": "string", - "description": - "URL location of the source this service is associated with", - "format": "url" - } - } - } - } - }, - "ServiceProxy": { - "description": - "Entirely used as the body for the request to the service backend.", - "type": "object" - }, - "ServiceProxyResponse": { - "description": "Entire response from the service backend.", - "type": "object" - }, - "Rules": { - "type": "object", - "required": ["rules"], - "properties": { - "rules": { - "type": "array", - "items": { - "$ref": "#/definitions/Rule" - } - } - } - }, - "Query": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "InfluxQL statement to be analyzed", - "example": { - "query": - "select max(usage_system) from telegraf.autogen.cpu group by time(10m)" - } - } - } - }, - "QueryResponse": { - "type": "object", - "properties": { - "query": { - "type": "string", - "example": { - "query": - "select max(usage_system) from telegraf.autogen.cpu group by time(10m)" - }, - "description": "InfluxQL statement to be analyzed" - }, - "queryConfig": { - "$ref": "#/definitions/QueryConfig" - } - } - }, - "Queries": { - "type": "object", - "properties": { - "queries": { - "type": "array", - "items": { - "$ref": "#/definitions/Query" - } - } - } - }, - "QueriesResponse": { - "type": "object", - "properties": { - "queries": { - "type": "array", - "items": { - "$ref": "#/definitions/QueryResponse" - } - } - } - }, - "QueryConfig": { - "type": "object", - "example": { - "id": "ce72917d-1ecb-45ea-a6cb-4c122deb93c7", - "database": "telegraf", - "measurement": "cpu", - "retentionPolicy": "autogen", - "fields": [ - { - "value": "max", - "type": "func", - "args": [ - { - "value": "usage_system", - "type": "field" - } - ] - } - ], - "tags": {}, - "groupBy": { - "time": "10m", - "tags": [] - }, - "areTagsAccepted": true, - "range": { - "lower": "15m", - "upper": "now" - } - }, - "properties": { - "id": { - "type": "string" - }, - "database": { - "type": "string" - }, - "measurement": { - "type": "string" - }, - "retentionPolicy": { - "type": "string" - }, - "areTagsAccepted": { - "type": "boolean" - }, - "rawText": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "groupBy": { - "type": "object", - "properties": { - "time": { - "type": "string" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": ["time", "tags"] - }, - "fields": { - "type": "array", - "items": { - "$ref": "#/definitions/Field" - } - }, - "range": { - "type": "object", - "properties": { - "lower": { - "type": "string" - }, - "upper": { - "type": "string" - } - }, - "required": ["lower", "upper"] - } - }, - "required": [ - "database", - "measurement", - "retentionPolicy", - "areTagsAccepted", - "tags", - "groupBy", - "fields" - ] - }, - "KapacitorNode": { - "type": "object", - "description": "Represents a node in the kapacitor TICKscript graph", - "required": ["name"], - "properties": { - "name": { - "type": "string", - "description": "Name of the kapacitor node e.g. slack" - }, - "args": { - "type": "array", - "description": "All arguments to the named node", - "items": { - "type": "string" - } - }, - "properties": { - "type": "array", - "description": "All properties attached to the kapacitor node", - "items": { - "$ref": "#/definitions/KapacitorProperty" - } - } - } - }, - "Field": { - "type": "object", - "required": ["type", "value"], - "description": "Represents a field to be returned from an InfluxQL query", - "properties": { - "value": { - "description": - "value is the value of the field. Meaning of the value is implied by the `type` key", - "type": "string" - }, - "type": { - "description": - "type describes the field type. func is a function; field is a field reference", - "type": "string", - "enum": ["func", "field", "integer", "number", "regex", "wildcard"] - }, - "alias": { - "description": - "Alias overrides the field name in the returned response. Applies only if type is `func`", - "type": "string" - }, - "args": { - "description": "Args are the arguments to the function", - "type": "array", - "items": { - "$ref": "#/definitions/Field" - } - } - } - }, - "KapacitorProperty": { - "type": "object", - "description": - "Represents a property attached to a node in the kapacitor TICKscript graph", - "required": ["name"], - "properties": { - "name": { - "type": "string", - "description": - "Name of the kapacitor property e.g. channel for a slack node" - }, - "args": { - "type": "array", - "description": "All arguments to the named property", - "items": { - "type": "string" - } - } - } - }, - "RetentionPolicies": { - "type": "object", - "required": ["retentionPolicies"], - "properties": { - "retentionPolicies": { - "type": "array", - "items": { - "$ref": "#/definitions/RetentionPolicy" - } - } - } - }, - "RetentionPolicy": { - "type": "object", - "required": ["name", "duration", "replication"], - "example": { - "name": "weekly", - "duration": "7d", - "replication": 1, - "shardDuration": "7d", - "default": true, - "links": { - "self": "/chronograf/v1/sources/1/dbs/NOAA_water_database/rps/liquid" - } - }, - "properties": { - "name": { - "type": "string", - "description": "The identifying name of the retention policy" - }, - "duration": { - "type": "string", - "description": "the duration of the retention policy" - }, - "replication": { - "type": "integer", - "format": "int32", - "description": "how many copies of the data are stored in the cluster" - }, - "shardDuration": { - "type": "string", - "description": "the interval spanned by each shard group" - }, - "default": { - "type": "boolean", - "description": - "Indicates whether this retention policy should be the default" - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - } - } - } - } - }, - "MeasurementsResponse": { - "type": "object", - "properties": { - "measurements": { - "type": "array", - "items": { - "$ref": "#/definitions/Measurement" - } - }, - "links": { - "type": "object", - "description": - "Links to paginated measurements, relative to the current page.", - "properties": { - "self": { - "type": "string", - "format": "url", - "description": "Current page measurements", - "required": true - }, - "first": { - "type": "string", - "format": "url", - "description": "First page of measurements", - "required": true - }, - "next": { - "type": "string", - "format": "url", - "description": "Next page of measurements", - "required": true - }, - "prev": { - "type": "string", - "format": "url", - "description": - "Previous page of measurements, if not at the first page.", - "required": false - } - } - } - }, - "example": { - "measurements": [ - { - "name": "alerts" - }, - { - "name": "annotations" - } - ], - "links": { - "self": - "/chronograf/v1/sources/1/dbs/chronograf/measurements?limit=100&offset=0", - "first": - "/chronograf/v1/sources/1/dbs/chronograf/measurements?limit=100&offset=0", - "next": - "/chronograf/v1/sources/1/dbs/chronograf/measurements?limit=100&offset=100" - } - } - }, - "Measurement": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Measurement name." - } - } - }, - "Rule": { - "type": "object", - "example": { - "id": "chronograf-v1-b2b065ea-79bd-4e4f-8c0d-d0ef68477d38", - "query": { - "id": "ce72917d-1ecb-45ea-a6cb-4c122deb93c7", - "database": "telegraf", - "measurement": "cpu", - "retentionPolicy": "autogen", - "fields": [ - { - "value": "max", - "type": "func", - "args": [ - { - "value": "usage_system", - "type": "field" - } - ] - } - ], - "tags": {}, - "groupBy": { - "time": "10m", - "tags": [] - }, - "areTagsAccepted": true - }, - "every": "30s", - "alerts": ["alerta"], - "alertNodes": [ - { - "name": "alerta", - "args": [], - "properties": [] - } - ], - "message": "too much spam", - "details": "muh body", - "trigger": "threshold", - "values": { - "operator": "greater than", - "value": "10" - }, - "name": "Untitled Rule", - "tickscript": - "var db = 'telegraf'\n\nvar rp = 'autogen'\n\nvar measurement = 'cpu'\n\nvar groupBy = []\n\nvar whereFilter = lambda: TRUE\n\nvar period = 10m\n\nvar every = 30s\n\nvar name = 'Untitled Rule'\n\nvar idVar = name + ':{{.Group}}'\n\nvar message = 'too much spam'\n\nvar idTag = 'alertID'\n\nvar levelTag = 'level'\n\nvar messageField = 'message'\n\nvar durationField = 'duration'\n\nvar outputDB = 'chronograf'\n\nvar outputRP = 'autogen'\n\nvar outputMeasurement = 'alerts'\n\nvar triggerType = 'threshold'\n\nvar details = 'muh body'\n\nvar crit = 10\n\nvar data = stream\n |from()\n .database(db)\n .retentionPolicy(rp)\n .measurement(measurement)\n .groupBy(groupBy)\n .where(whereFilter)\n |window()\n .period(period)\n .every(every)\n .align()\n |max('usage_system')\n .as('value')\n\nvar trigger = data\n |alert()\n .crit(lambda: \"value\" > crit)\n .stateChangesOnly()\n .message(message)\n .id(idVar)\n .idTag(idTag)\n .levelTag(levelTag)\n .messageField(messageField)\n .durationField(durationField)\n .details(details)\n .alerta()\n\ntrigger\n |influxDBOut()\n .create()\n .database(outputDB)\n .retentionPolicy(outputRP)\n .measurement(outputMeasurement)\n .tag('alertName', name)\n .tag('triggerType', triggerType)\n\ntrigger\n |httpOut('output')\n", - "type": "stream", - "dbrps": [ - { - "db": "telegraf", - "rp": "autogen" - } - ], - "status": "disabled", - "executing": false, - "error": "", - "created": "2017-05-05T16:16:03.471138388-05:00", - "modified": "2017-05-23T15:57:42.625909746-05:00", - "last-enabled": "2017-05-05T16:16:25.890210217-05:00", - "links": { - "self": - "/chronograf/v1/sources/5/kapacitors/5/rules/chronograf-v1-b2b065ea-79bd-4e4f-8c0d-d0ef68477d38", - "kapacitor": - "/chronograf/v1/sources/5/kapacitors/5/proxy?path=%2Fkapacitor%2Fv1%2Ftasks%2Fchronograf-v1-b2b065ea-79bd-4e4f-8c0d-d0ef68477d38", - "output": - "/chronograf/v1/sources/5/kapacitors/5/proxy?path=%2Fkapacitor%2Fv1%2Ftasks%2Fchronograf-v1-b2b065ea-79bd-4e4f-8c0d-d0ef68477d38%2Foutput" - } - }, - "required": ["query", "every", "trigger"], - "properties": { - "id": { - "type": "string", - "description": "ID for this rule; the ID is shared with kapacitor" - }, - "query": { - "$ref": "#/definitions/QueryConfig" - }, - "name": { - "type": "string", - "description": "User facing name of the alerting rule" - }, - "every": { - "type": "string", - "description": - "Golang duration string specifying how often the alert condition is checked" - }, - "alerts": { - "type": "array", - "description": - "Array of alerting services to warn if the alert is triggered", - "items": { - "type": "string", - "enum": [ - "alerta", - "post", - "http", - "hipchat", - "kafka", - "opsgenie", - "opsgenie2", - "pagerduty", - "pagerduty2", - "victorops", - "email", - "exec", - "log", - "pushover", - "sensu", - "slack", - "smtp", - "talk", - "telegram", - "tcp" - ] - } - }, - "alertNodes": { - "type": "array", - "description": "Arguments and properties to add to alert", - "items": { - "$ref": "#/definitions/KapacitorNode" - } - }, - "message": { - "type": "string", - "description": "Message to send when alert occurs." - }, - "details": { - "type": "string", - "description": - "Template for constructing a detailed HTML message for the alert. (Currently, only used for email/smtp" - }, - "trigger": { - "type": "string", - "description": - "Trigger defines the alerting structure; deadman alert if no data are received for the specified time range; relative alert if the data change relative to the data in a different time range; threshold alert if the data cross a boundary", - "enum": ["deadman", "relative", "threshold"] - }, - "values": { - "type": "object", - "description": "Alerting logic for trigger type", - "properties": { - "change": { - "description": "Specifies if the change is percent or absolute", - "type": "string", - "enum": ["% change", "change"] - }, - "period": { - "description": - "Length of time before deadman is alerted (golang duration)", - "type": "string" - }, - "shift": { - "description": - "Amount of time to look into the past to compare to the present (golang duration)", - "type": "string" - }, - "operator": { - "description": "Operator for alert comparison", - "type": "string", - "enum": [ - "greater than", - "less than", - "equal to or less than", - "equal to or greater", - "equal to", - "not equal to", - "inside range", - "outside range" - ] - }, - "value": { - "description": - "Value is the boundary value when alert goes critical", - "type": "string" - }, - "rangeValue": { - "description": "Optional value for range comparisions", - "type": "string" - } - } - }, - "dbrps": { - "type": "array", - "description": - "List of database retention policy pairs the task is allowed to access.", - "items": { - "$ref": "#/definitions/DBRP" - } - }, - "tickscript": { - "type": "string", - "description": "TICKscript representing this rule" - }, - "status": { - "type": "string", - "description": - "Represents if this rule is enabled or disabled in kapacitor", - "enum": ["enabled", "disabled"] - }, - "executing": { - "type": "boolean", - "description": "Whether the task is currently executing.", - "readOnly": true - }, - "type": { - "type": "string", - "description": - "Represents the task type where stream is data streamed to kapacitor and batch is queried by kapacitor.", - "enum": ["stream", "batch"] - }, - "error": { - "type": "string", - "description": - "Any error encountered when kapacitor executes the task.", - "readOnly": true - }, - "created": { - "type": "string", - "description": "Date the task was first created", - "readOnly": true - }, - "modified": { - "type": "string", - "description": "Date the task was last modified", - "readOnly": true - }, - "last-enabled": { - "type": "string", - "description": "Date the task was last set to status enabled", - "readOnly": true - }, - "links": { - "type": "object", - "required": ["self", "kapacitor"], - "properties": { - "self": { - "description": "Self link pointing to this rule resource", - "type": "string", - "format": "uri" - }, - "kapacitor": { - "description": - "Link pointing to the kapacitor proxy for this rule including the path query parameter.", - "type": "string", - "format": "uri" - }, - "output": { - "description": - "Link pointing to the kapacitor httpOut node of the tickscript; includes the path query argument", - "type": "string", - "format": "uri" - } - } - } - } - }, - "DBRP": { - "type": "object", - "description": "Database retention policy pair", - "properties": { - "db": { - "description": "Database name", - "type": "string" - }, - "rp": { - "description": "Retention policy", - "type": "string" - } - }, - "required": ["db", "rp"] - }, - "Sources": { - "type": "array", - "items": { - "$ref": "#/definitions/Source" - } - }, - "Source": { - "type": "object", - "example": { - "id": "4", - "name": "Influx 1", - "type": "influx", - "url": "http://localhost:8086", - "default": false, - "telegraf": "telegraf", - "defaultRP": "customRP", - "organization": "default", - "authentication": "basic", - "role": "viewer", - "links": { - "self": "/chronograf/v1/sources/4", - "kapacitors": "/chronograf/v1/sources/4/kapacitors", - "proxy": "/chronograf/v1/sources/4/proxy", - "write": "/chronograf/v1/sources/4/write", - "queries": "/chronograf/v1/sources/4/queries", - "permissions": "/chronograf/v1/sources/4/permissions", - "users": "/chronograf/v1/sources/4/users", - "roles": "/chronograf/v1/sources/4/roles", - "health": "/chronograf/v1/sources/4/health" - } - }, - "required": ["url"], - "properties": { - "id": { - "type": "string", - "description": - "Unique identifier representing a specific data source.", - "readOnly": true - }, - "name": { - "type": "string", - "description": "User facing name of data source" - }, - "type": { - "type": "string", - "description": "Format of the data source", - "readOnly": true, - "enum": ["influx", "influx-enterprise", "influx-relay"] - }, - "username": { - "type": "string", - "description": "Username for authentication to data source" - }, - "password": { - "type": "string", - "description": "Password is in cleartext." - }, - "sharedSecret": { - "type": "string", - "description": - "JWT signing secret for optional Authorization: Bearer to InfluxDB" - }, - "url": { - "type": "string", - "format": "url", - "description": - "URL for the time series data source backend (e.g. http://localhost:8086)" - }, - "metaUrl": { - "type": "string", - "format": "url", - "description": "URL for the influxdb meta node" - }, - "insecureSkipVerify": { - "type": "boolean", - "description": - "True means any certificate presented by the source is accepted. Typically used for self-signed certs. Probably should only be used for testing." - }, - "default": { - "type": "boolean", - "description": "Indicates whether this source is the default source" - }, - "telegraf": { - "type": "string", - "description": - "Database where telegraf information is stored for this source", - "default": "telegraf" - }, - "defaultRP": { - "type": "string", - "description": - "Default retention policy used in Host-related queries proxied to InfluxDB from the Host List and Host pages.", - "default": "" - }, - "organization": { - "type": "string", - "description": - "Organization that this source belongs to, when Chronograf auth is in use", - "default": "default" - }, - "role": { - "type": "string", - "description": - "Not used currently. Can be used to designate a minimum role required to access this source.", - "default": "viewer" - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - }, - "proxy": { - "type": "string", - "description": "URL location of proxy endpoint for this source", - "format": "url" - }, - "write": { - "type": "string", - "description": "URL location of write endpoint for this source", - "format": "url" - }, - "queries": { - "type": "string", - "description": - "URL location of the queries endpoint for this source", - "format": "url" - }, - "kapacitors": { - "type": "string", - "description": - "URL location of the kapacitors endpoint for this source", - "format": "url" - }, - "users": { - "type": "string", - "description": - "URL location of the users endpoint for this source", - "format": "url" - }, - "permissions": { - "type": "string", - "description": - "URL location of the permissions endpoint for this source", - "format": "url" - }, - "roles": { - "type": "string", - "description": - "Optional path to the roles endpoint IFF it is supported on this source", - "format": "url" - }, - "health": { - "type": "string", - "description": "Path to determine if source is healthy", - "format": "url" - } - } - } - } - }, - "Proxy": { - "type": "object", - "example": { - "query": "select $myfield from cpu where time > now() - 10m", - "db": "telegraf", - "rp": "autogen", - "tempVars": [ - { - "tempVar": ":myfield:", - "values": [ - { - "type": "fieldKey", - "value": "usage_user" - } - ] - } - ] - }, - "required": ["query"], - "properties": { - "query": { - "type": "string" - }, - "db": { - "type": "string" - }, - "rp": { - "type": "string" - }, - "epoch": { - "description": "timestamp return format", - "type": "string", - "enum": ["h", "m", "s", "ms", "u", "ns"] - }, - "tempVars": { - "type": "array", - "description": - "Template variables to replace within an InfluxQL query", - "items": { - "$ref": "#/definitions/TemplateVariable" - } - } - } - }, - "TemplateVariable": { - "type": "object", - "description": - "Named variable within an InfluxQL query to be replaced with values", - "properties": { - "tempVar": { - "type": "string", - "description": "String to replace within an InfluxQL statement" - }, - "values": { - "type": "array", - "description": "Values used to replace tempVar.", - "items": { - "$ref": "#/definitions/TemplateValue" - } - } - } - }, - "TemplateValue": { - "type": "object", - "description": - "Value use to replace a template in an InfluxQL query. The type governs the output format", - "properties": { - "value": { - "type": "string", - "description": "Specific value that will be encoded based on type" - }, - "type": { - "type": "string", - "enum": ["csv", "tagKey", "tagValue", "fieldKey", "timeStamp", "map"], - "description": - "The type will change the format of the output value. tagKey/fieldKey are double quoted; tagValue are single quoted; csv and timeStamp are not quoted." - }, - "key": { - "type": "string", - "description":"This will be the key for a specific value of a template variable. Used if the templateVar type is 'map'" - } - } - }, - "ProxyResponse": { - "type": "object", - "example": { - "results": [ - { - "statement_id": 0, - "series": [ - { - "name": "cpu", - "columns": [ - "time", - "cpu", - "host", - "usage_guest", - "usage_guest_nice", - "usage_idle", - "usage_iowait", - "usage_irq", - "usage_nice", - "usage_softirq", - "usage_steal", - "usage_system", - "usage_user" - ], - "values": [ - [ - 1487785510000, - "cpu-total", - "ChristohersMBP2.lan", - 0, - 0, - 76.6916354556804, - 0, - 0, - 0, - 0, - 0, - 4.781523096129837, - 18.526841448189764 - ] - ] - } - ] - } - ] - }, - "properties": { - "results": { - "description": "results from influx", - "type": "object" - } - } - }, - "InfluxDB-Roles": { - "type": "array", - "items": { - "$ref": "#/definitions/InfluxDB-Role" - }, - "example": { - "roles": [ - { - "users": [ - { - "name": "admin", - "links": { - "self": "/chronograf/v1/sources/3/users/admin" - } - } - ], - "name": "timetravelers", - "permissions": [ - { - "scope": "database", - "name": "telegraf", - "allowed": ["ReadData", "WriteData"] - } - ], - "links": { - "self": "/chronograf/v1/sources/3/roles/timetravelers" - } - } - ] - } - }, - "InfluxDB-Role": { - "type": "object", - "required": ["name"], - "properties": { - "name": { - "type": "string", - "description": "Unique name of the role", - "maxLength": 254, - "minLength": 1 - }, - "users": { - "$ref": "#/definitions/InfluxDB-Users" - }, - "permissions": { - "$ref": "#/definitions/InfluxDB-Permissions" - }, - "links": { - "type": "object", - "description": "URL relations of this role", - "properties": { - "self": { - "type": "string", - "format": "url", - "description": "URI of resource." - } - } - } - }, - "example": { - "users": [ - { - "name": "admin", - "links": { - "self": "/chronograf/v1/sources/3/users/admin" - } - } - ], - "name": "timetravelers", - "permissions": [ - { - "scope": "database", - "name": "telegraf", - "allowed": ["ReadData", "WriteData"] - } - ], - "links": { - "self": "/chronograf/v1/sources/3/roles/timetravelers" - } - } - }, - "InfluxDB-Users": { - "type": "object", - "properties": { - "users": { - "type": "array", - "items": { - "$ref": "#/definitions/InfluxDB-User" - } - } - }, - "example": { - "users": [ - { - "name": "docbrown", - "permissions": [ - { - "scope": "all", - "allowed": [ - "ViewAdmin", - "ViewChronograf", - "CreateDatabase", - "CreateUserAndRole", - "DropDatabase", - "DropData", - "ReadData", - "WriteData", - "ManageShard", - "ManageContinuousQuery", - "ManageQuery", - "ManageSubscription", - "Monitor", - "KapacitorAPI" - ] - } - ], - "roles": [ - { - "name": "timetravelers", - "permissions": [ - { - "scope": "database", - "name": "telegraf", - "allowed": ["ReadData", "WriteData"] - } - ], - "links": { - "self": "/chronograf/v1/sources/3/roles/timetravelers" - } - } - ], - "links": { - "self": "/chronograf/v1/sources/3/users/docbrown" - } - } - ] - } - }, - "InfluxDB-User": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Unique name of the user", - "maxLength": 254, - "minLength": 1 - }, - "password": { - "type": "string" - }, - "permissions": { - "$ref": "#/definitions/InfluxDB-Permissions" - }, - "roles": { - "$ref": "#/definitions/InfluxDB-Roles" - }, - "links": { - "type": "object", - "description": "URL relations of this user", - "properties": { - "self": { - "type": "string", - "format": "url", - "description": "URI of resource." - } - } - } - }, - "example": { - "name": "docbrown", - "permissions": [ - { - "scope": "all", - "allowed": [ - "ViewAdmin", - "ViewChronograf", - "CreateDatabase", - "CreateUserAndRole", - "DropDatabase", - "DropData", - "ReadData", - "WriteData", - "ManageShard", - "ManageContinuousQuery", - "ManageQuery", - "ManageSubscription", - "Monitor", - "KapacitorAPI" - ] - } - ], - "roles": [ - { - "name": "timetravelers", - "permissions": [ - { - "scope": "database", - "name": "telegraf", - "allowed": ["ReadData", "WriteData"] - } - ], - "links": { - "self": "/chronograf/v1/sources/3/roles/timetravelers" - } - } - ], - "links": { - "self": "/chronograf/v1/sources/3/users/docbrown" - } - } - }, - "InfluxDB-Permissions": { - "description": - "Permissions represent the entire set of permissions a InfluxDB User or InfluxDB Role may have", - "type": "array", - "items": { - "$ref": "#/definitions/InfluxDB-Permission" - } - }, - "InfluxDB-Permission": { - "description": - "Permission is a specific allowance for InfluxDB User or InfluxDB Role bound to a scope of the data source", - "type": "object", - "required": ["scope", "allowed"], - "properties": { - "scope": { - "type": "string", - "description": - "Describes if the permission is for all databases or restricted to one database", - "enum": ["all", "database"] - }, - "name": { - "type": "string", - "description": - "If the scope is database this identifies the name of the database" - }, - "allowed": { - "$ref": "#/definitions/InfluxDB-Allowances" - } - }, - "example": { - "scope": "database", - "name": "telegraf", - "allowed": ["READ", "WRITE"] - } - }, - "AllPermissions": { - "description": - "All possible permissions for this particular datasource. Used as a static list", - "type": "object", - "properties": { - "permissions": { - "$ref": "#/definitions/InfluxDB-Permissions" - }, - "links": { - "type": "object", - "properties": { - "self": { - "description": "Relative link back to the permissions endpoint", - "type": "string", - "format": "uri" - }, - "source": { - "description": "Relative link to host with these permissiosn", - "type": "string", - "format": "uri" - } - } - } - } - }, - "InfluxDB-Allowances": { - "description": - "Allowances defines what actions a user can have on a scoped permission", - "type": "array", - "items": { - "type": "string", - "description": - "OSS InfluxDB is READ and WRITE. Enterprise is all others", - "enum": [ - "READ", - "WRITE", - "NoPermissions", - "ViewAdmin", - "ViewChronograf", - "CreateDatabase", - "CreateUserAndRole", - "AddRemoveNode", - "DropDatabase", - "DropData", - "ReadData", - "WriteData", - "Rebalance", - "ManageShard", - "ManageContinuousQuery", - "ManageQuery", - "ManageSubscription", - "Monitor", - "CopyShard", - "KapacitorAPI", - "KapacitorConfigAPI" - ] - } - }, - "Layouts": { - "required": ["layouts"], - "type": "object", - "properties": { - "layouts": { - "type": "array", - "items": { - "$ref": "#/definitions/Layout" - } - } - } - }, - "Layout": { - "type": "object", - "required": ["cells", "app", "measurement"], - "properties": { - "id": { - "type": "string", - "description": - "ID is an opaque string that uniquely identifies this layout." - }, - "app": { - "type": "string", - "description": "App is the user facing name of this Layout" - }, - "measurement": { - "type": "string", - "description": - "Measurement is the descriptive name of the time series data." - }, - "cells": { - "type": "array", - "description": "Cells are the individual visualization elements.", - "items": { - "$ref": "#/definitions/Cell" - } - }, - "link": { - "$ref": "#/definitions/Link" - } - }, - "example": { - "id": "0e980b97-c162-487b-a815-3f955df62430", - "app": "docker", - "measurement": "docker_container_net", - "autoflow": true, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "i": "4c79cefb-5152-410c-9b88-74f9bff7ef23", - "name": "Docker - Container Network", - "queries": [ - { - "query": - "SELECT derivative(mean(\"tx_bytes\"), 10s) AS \"net_tx_bytes\" FROM \"docker_container_net\"", - "groupbys": ["\"container_name\""] - }, - { - "query": - "SELECT derivative(mean(\"rx_bytes\"), 10s) AS \"net_rx_bytes\" FROM \"docker_container_net\"", - "groupbys": ["\"container_name\""] - } - ], - "type": "" - } - ], - "link": { - "href": "/chronograf/v1/layouts/0e980b97-c162-487b-a815-3f955df62430", - "rel": "self" - } - } - }, - "Mappings": { - "type": "object", - "required": ["mappings"], - "properties": { - "mappings": { - "type": "array", - "items": { - "$ref": "#/definitions/Mapping" - } - } - } - }, - "Mapping": { - "type": "object", - "required": ["measurement", "name"], - "properties": { - "measurement": { - "description": "The measurement where data for this mapping is found", - "type": "string" - }, - "name": { - "description": - "The application name which will be assigned to the corresponding measurement", - "type": "string" - } - }, - "example": { - "measurement": "riak", - "name": "riak" - } - }, - "Cell": { - "type": "object", - "required": ["i", "x", "y", "w", "h"], - "properties": { - "i": { - "description": "Unique ID of Cell", - "type": "string", - "format": "uuid4" - }, - "x": { - "description": "X-coordinate of Cell in the Dashboard", - "type": "integer", - "format": "int32" - }, - "y": { - "description": "Y-coordinate of Cell in the Dashboard", - "type": "integer", - "format": "int32" - }, - "w": { - "description": "Width of Cell in the Dashboard", - "type": "integer", - "format": "int32", - "minimum": 1, - "default": 4 - }, - "h": { - "description": "Height of Cell in the Dashboard", - "type": "integer", - "format": "int32", - "minimum": 1, - "default": 4 - }, - "name": { - "description": "Title of Cell in the Dashboard", - "type": "string" - }, - "queries": { - "description": "Time-series data queries for Cell", - "type": "array", - "items": { - "$ref": "#/definitions/DashboardQuery" - } - }, - "axes": { - "description": "The viewport for a Cell's visualizations", - "type": "object", - "properties": { - "x": { - "$ref": "#/definitions/Axis" - }, - "y": { - "$ref": "#/definitions/Axis" - }, - "y2": { - "$ref": "#/definitions/Axis" - } - } - }, - "type": { - "description": "Cell visualization type", - "type": "string", - "enum": [ - "single-stat", - "line", - "line-plus-single-stat", - "line-stacked", - "line-stepplot", - "bar", - "gauge", - "table" - ], - "default": "line" - }, - "colors": { - "description": "Colors define encoding data into a visualization", - "type": "array", - "items": { - "$ref": "#/definitions/DashboardColor" - } - }, - "legend": { - "description": - "Legend define encoding of the data into a cell's legend", - "type": "object", - "properties": { - "type": { - "description": "type is the style of the legend", - "type": "string", - "enum": ["static"] - }, - "orientation": { - "description": - "orientation is the location of the legend with respect to the cell graph", - "type": "string", - "enum": ["top", "bottom", "left", "right"] - } - } - }, - "tableOptions": { - "verticalTimeAxis": { - "description": - "verticalTimeAxis describes the orientation of the table by indicating whether the time axis will be displayed vertically", - "type": "boolean" - }, - "sortBy": { - "description": - "sortBy contains the name of the series that is used for sorting the table", - "type": "object", - "$ref": "#/definitions/RenamableField" - }, - "wrapping": { - "description": - "wrapping describes the text wrapping style to be used in table cells", - "type": "string", - "enum": ["truncate", "wrap", "single-line"] - }, - "fixFirstColumn": { - "description": - "fixFirstColumn indicates whether the first column of the table should be locked", - "type": "boolean" - } - }, - "fieldOptions": { - "description": - "fieldOptions represent the fields retrieved by the query with customization options", - "type": "array", - "items": { - "$ref": "#/definitions/RenamableField" - } - }, - "timeFormat": { - "description": - "timeFormat describes the display format for time values according to moment.js date formatting", - "type": "string" - }, - "decimalPoints": { - "description": - "decimal points indicates whether and how many digits to show after decimal point", - "type": "object", - "properties": { - "isEnforced": { - "description": - "Indicates whether decimal point setting should be enforced", - "type": "bool" - }, - "digits": { - "description": "The number of digits after decimal to display", - "type": "integer" - } - } - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - } - } - } - }, - "example": { - "x": 5, - "y": 5, - "w": 4, - "h": 4, - "name": "usage_user", - "queries": [ - { - "query": - "SELECT mean(\"usage_user\") AS \"usage_user\" FROM \"cpu\"", - "label": "%" - } - ], - "type": "line" - } - }, - "LayoutQuery": { - "type": "object", - "required": ["query"], - "properties": { - "label": { - "description": "Optional Y-axis user-facing label for this query", - "type": "string" - }, - "range": { - "description": "Optional default range of the Y-axis", - "type": "object", - "required": ["upper", "lower"], - "properties": { - "upper": { - "description": "Upper bound of the display range of the Y-axis", - "type": "integer", - "format": "int64" - }, - "lower": { - "description": "Lower bound of the display range of the Y-axis", - "type": "integer", - "format": "int64" - } - } - }, - "query": { - "type": "string" - }, - "wheres": { - "description": "Defines the condition clauses for influxdb", - "type": "array", - "items": { - "type": "string" - } - }, - "groupbys": { - "description": "Defines the group by clauses for influxdb", - "type": "array", - "items": { - "type": "string" - } - } - }, - "example": { - "label": "# warnings", - "query": - "SELECT count(\"check_id\") as \"Number Warning\" FROM consul_health_checks", - "wheres": ["\"status\" = 'warning'"], - "groupbys": ["\"service_name\""] - } - }, - "DashboardQuery": { - "type": "object", - "required": ["query"], - "properties": { - "label": { - "description": "Optional Y-axis user-facing label for this query", - "type": "string" - }, - "range": { - "description": "Optional default range of the Y-axis", - "type": "object", - "required": ["upper", "lower"], - "properties": { - "upper": { - "description": "Upper bound of the display range of the Y-axis", - "type": "integer", - "format": "int64" - }, - "lower": { - "description": "Lower bound of the display range of the Y-axis", - "type": "integer", - "format": "int64" - } - } - }, - "query": { - "type": "string" - }, - "source": { - "type": "string", - "format": "url", - "description": "Optional URI for data source for this query" - }, - "queryConfig": { - "$ref": "#/definitions/QueryConfig" - } - }, - "example": { - "id": 4, - "cells": [ - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "name": "", - "queries": [ - { - "query": - "SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"cpu\"", - "label": "%", - "queryConfig": { - "database": "", - "measurement": "cpu", - "retentionPolicy": "", - "fields": [ - { - "value": "mean", - "type": "func", - "alias": "mean_usage_user", - "args": [ - { - "value": "usage_user", - "type": "field" - } - ] - } - ], - "tags": {}, - "groupBy": { - "time": "", - "tags": [] - }, - "areTagsAccepted": false - } - } - ], - "type": "line" - } - ], - "name": "dashboard name", - "links": { - "self": "/chronograf/v1/dashboards/4" - } - } - }, - "Dashboards": { - "description": "a list of dashboards", - "type": "object", - "properties": { - "dashboards": { - "type": "array", - "items": { - "$ref": "#/definitions/Dashboard" - } - } - } - }, - "Dashboard": { - "type": "object", - "properties": { - "id": { - "description": "the unique dashboard id", - "type": "integer", - "format": "int64" - }, - "cells": { - "type": "array", - "items": { - "$ref": "#/definitions/Cell" - } - }, - "name": { - "description": "the user-facing name of the dashboard", - "type": "string" - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "string", - "description": "Self link mapping to this resource", - "format": "url" - } - } - } - }, - "example": { - "id": 4, - "cells": [ - { - "x": 5, - "y": 5, - "w": 4, - "h": 4, - "name": "usage_user", - "queries": [ - { - "query": - "SELECT mean(\"usage_user\") AS \"usage_user\" FROM \"cpu\"", - "db": "telegraf", - "label": "%" - } - ], - "type": "line" - }, - { - "x": 0, - "y": 0, - "w": 4, - "h": 4, - "name": "usage_system", - "queries": [ - { - "query": - "SELECT mean(\"usage_system\") AS \"usage_system\" FROM \"cpu\"", - "db": "telegraf", - "label": "%" - } - ], - "type": "line" - } - ], - "name": "lalalalala", - "links": { - "self": "/chronograf/v1/dashboards/4" - } - } - }, - "DashboardColor": { - "type": "object", - "description": - "Color defines an encoding of a data value into color space", - "properties": { - "id": { - "description": "ID is the unique id of the cell color", - "type": "string" - }, - "type": { - "description": "Type is how the color is used.", - "type": "string", - "enum": ["min", "max", "threshold"] - }, - "hex": { - "description": "Hex is the hex number of the color", - "type": "string", - "maxLength": 7, - "minLength": 7 - }, - "name": { - "description": "Name is the user-facing name of the hex color", - "type": "string" - }, - "value": { - "description": "Value is the data value mapped to this color", - "type": "string" - } - } - }, - "Axis": { - "type": "object", - "description": "A description of a particular axis for a visualization", - "properties": { - "bounds": { - "type": "array", - "minItems": 0, - "maxItems": 2, - "description": - "The extents of an axis in the form [lower, upper]. Clients determine whether bounds are to be inclusive or exclusive of their limits", - "items": { - "type": "integer", - "format": "int64" - } - }, - "label": { - "description": "label is a description of this Axis", - "type": "string" - }, - "prefix": { - "description": - "Prefix represents a label prefix for formatting axis values.", - "type": "string" - }, - "suffix": { - "description": - "Suffix represents a label suffix for formatting axis values.", - "type": "string" - }, - "base": { - "description": - "Base represents the radix for formatting axis values.", - "type": "string" - }, - "scale": { - "description": - "Scale is the axis formatting scale. Supported: \"log\", \"linear\"", - "type": "string" - } - } - }, - "RenamableField": { - "description": - "Describes a field that can be renamed and made visible or invisible", - "type": "object", - "properties": { - "internalName": { - "description": "This is the calculated name of a field", - "readOnly": true, - "type": "string" - }, - "displayName": { - "description": - "This is the name that a field is renamed to by the user", - "type": "string" - }, - "visible": { - "description": - "Indicates whether this field should be visible on the table", - "type": "boolean" - } - } - }, - "Config": { - "description": "Global application configuration", - "type": "object", - "properties": { - "auth": { - "$ref": "#/definitions/AuthConfig" - } - }, - "example": { - "auth": { - "superAdminNewUsers": true - } - } - }, - "AuthConfig": { - "description": "Global application configuration for auth", - "type": "object", - "required": ["superAdminNewUsers"], - "properties": { - "superAdminNewUsers": { - "type": "boolean", - "default": true - } - }, - "example": { - "superAdminNewUsers": true - } - }, - "OrganizationConfig": { - "description": "Configurations for a specific organization", - "type": "object", - "required": ["logViewer"], - "properties": { - "organization": { - "type": "string", - "readOnly": true - }, - "logViewer": { - "$ref": "#/definitions/LogViewerConfig" - } - }, - "example": { - "organization": "default", - "logViewer": { - "columns": [ - { - "name": "severity", - "position": 0, - "encodings": [ - { - "type": "label", - "value": "icon" - }, - { - "type": "label", - "value": "text" - }, - { - "type": "visibility", - "value": "visible" - }, - { - "type": "color", - "name": "ruby", - "value": "emergency" - }, - { - "type": "color", - "name": "rainforest", - "value": "info" - }, - { - "type": "displayName", - "value": "Log Severity!" - } - ] - }, - { - "name": "messages", - "position": 1, - "encodings": [ - { - "type": "visibility", - "value": "hidden" - } - ] - } - ] - } - } - }, - "LogViewerConfig": { - "description": "Contains the organization-specific configuration for the log viewer", - "type": "object", - "required": ["columns"], - "properties": { - "columns": { - "description": "Defines the order, names, and visibility of columns in the log viewer table", - "type": "array", - "items": { - "$ref": "#/definitions/LogViewerColumn" - } - } - }, - "example": { - "columns": [ - { - "name": "severity", - "position": 0, - "encodings": [ - { - "type": "label", - "value": "icon" - }, - { - "type": "label", - "value": "text" - }, - { - "type": "visibility", - "value": "visible" - }, - { - "type": "color", - "name": "ruby", - "value": "emergency" - }, - { - "type": "color", - "name": "rainforest", - "value": "info" - }, - { - "type": "displayName", - "value": "Log Severity!" - } - ] - }, - { - "name": "messages", - "position": 1, - "encodings": [ - { - "type": "visibility", - "value": "hidden" - } - ] - } - ] - } - }, - "LogViewerColumn": { - "description": "Contains the organization-specific configuration for the log viewer", - "type": "object", - "required": [ - "name", - "encodings", - "position" - ], - "properties": { - "name": { - "description": "Unique identifier name of the column", - "type": "string" - }, - "position": { - "type": "integer", - "format": "int32" - }, - "encodings": { - "description": "Composable encoding options for the column", - "type": "array", - "items": { - "description":"Type and value and optional name of an encoding", - "type": "object", - "required": ["type", "value"], - "properties": { - "type": { - "type": "string" - }, - "value": { - "type": "string" - }, - "name": { - "type": "string" - } - } - } - } - }, - "example": { - "name": "severity", - "position": 0, - "encodings": [ - { - "type": "label", - "value": "icon" - }, - { - "type": "label", - "value": "text" - }, - { - "type": "visibility", - "value": "visible" - }, - { - "type": "color", - "name": "ruby", - "value": "emergency" - }, - { - "type": "color", - "name": "rainforest", - "value": "info" - }, - { - "type": "displayName", - "value": "Log Severity!" - } - ] - } - }, - "Routes": { - "type": "object", - "properties": { - "me": { - "description": "Location of the me endpoint.", - "type": "string", - "format": "url" - }, - "layouts": { - "description": "Location of the layouts endpoint", - "type": "string", - "format": "url" - }, - "sources": { - "description": "Location of the sources endpoint", - "type": "string", - "format": "url" - }, - "mappings": { - "description": "Location of the application mappings endpoint", - "type": "string", - "format": "url" - }, - "dashboards": { - "description": "location of the dashboards endpoint", - "type": "string", - "format": "url" - }, - "external": { - "description": - "external links provided to client, ex. status feed URL", - "type": "object", - "properties": { - "statusFeed": { - "description": - "link to a JSON Feed for the News Feed on client's Status Page", - "type": "string", - "format": "url" - }, - "custom": { - "description": - "a collection of custom links set by the user to be rendered in the client User menu", - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "url": { - "type": "string", - "format": "url" - } - } - } - } - } - } - }, - "example": { - "layouts": "/chronograf/v1/layouts", - "mappings": "/chronograf/v1/mappings", - "sources": "/chronograf/v1/sources", - "me": "/chronograf/v1/me", - "dashboards": "/chronograf/v1/dashboards", - "external": { - "statusFeed": "http://news.influxdata.com/feed.json", - "custom": [ - { - "name": "InfluxData", - "url": "https://www.influxdata.com" - } - ] - } - } - }, - "Link": { - "type": "object", - "required": ["rel", "href"], - "readOnly": true, - "description": "URI of resource.", - "properties": { - "rel": { - "type": "string" - }, - "href": { - "type": "string", - "format": "url" - } - } - }, - "Error": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - } - } - } - } -} diff --git a/chronograf/server/swagger_v2.yml b/chronograf/server/swagger_v2.yml deleted file mode 100644 index d413a817e4f..00000000000 --- a/chronograf/server/swagger_v2.yml +++ /dev/null @@ -1,659 +0,0 @@ -openapi: "3.0.0" -info: - title: Chronograf - version: 1.5.0.0 -servers: - - url: /chronograf/v2 -paths: - /cells: - post: - tags: - - Cells - summary: Create a cell - requestBody: - description: cell to create - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Cell" - responses: - '201': - description: Added cell - content: - application/json: - schema: - $ref: "#/components/schemas/Cell" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - get: - tags: - - Cells - summary: Get all cells - responses: - '200': - description: all cells - content: - application/json: - schema: - $ref: "#/components/schemas/Cells" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '/cells/{cellID}': - get: - tags: - - Cells - summary: Get a single Cell - parameters: - - in: path - name: cellID - schema: - type: string - required: true - description: ID of cell to update - responses: - '200': - description: get a single cell - content: - application/json: - schema: - $ref: "#/components/schemas/Cell" - '404': - description: cell not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - patch: - tags: - - Cells - summary: Update a single cell - requestBody: - description: patching of a cell - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Cell" - parameters: - - in: path - name: cellID - schema: - type: string - required: true - description: ID of cell to update - responses: - '200': - description: Updated cell - content: - application/json: - schema: - $ref: "#/components/schemas/Cell" - '404': - description: cell not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - delete: - tags: - - Cells - summary: Delete a cell - parameters: - - in: path - name: cellID - schema: - type: string - required: true - description: ID of cell to update - responses: - '204': - description: delete has been accepted - '404': - description: cell not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /dashboards: - post: - tags: - - Dashboards - summary: Create a dashboard - requestBody: - description: dashboard to create - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Dashboard" - responses: - '201': - description: Added dashboard - content: - application/json: - schema: - $ref: "#/components/schemas/Dashboard" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - get: - tags: - - Dashboards - summary: Get all dashboards - responses: - '200': - description: all dashboards - content: - application/json: - schema: - $ref: "#/components/schemas/Dashboards" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '/dashboards/{dashboardID}': - get: - tags: - - Dashboards - summary: Get a single Dashboard - parameters: - - in: path - name: dashboardID - schema: - type: string - required: true - description: ID of dashboard to update - responses: - '200': - description: get a single dashboard - content: - application/json: - schema: - $ref: "#/components/schemas/Dashboard" - '404': - description: dashboard not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - patch: - tags: - - Dashboards - summary: Update a single dashboard - requestBody: - description: patching of a dashboard - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Dashboard" - parameters: - - in: path - name: dashboardID - schema: - type: string - required: true - description: ID of dashboard to update - responses: - '200': - description: Updated dashboard - content: - application/json: - schema: - $ref: "#/components/schemas/Dashboard" - '404': - description: dashboard not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - delete: - tags: - - Dashboards - summary: Delete a dashboard - parameters: - - in: path - name: dashboardID - schema: - type: string - required: true - description: ID of dashboard to update - responses: - '204': - description: delete has been accepted - '404': - description: dashboard not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - default: - description: unexpected error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" -components: - schemas: - Link: - type: object - readOnly: true - description: URI of resource. - properties: - href: - type: string - format: url - required: [href] - Links: - type: object - readOnly: true - properties: - self: - $ref: "#/components/schemas/Link" - required: [self] - Field: - type: object - properties: - value: - description: >- - value is the value of the field. Meaning of the value is implied by - the `type` key - type: string - type: - description: >- - type describes the field type. func is a function; field is a field - reference - type: string - enum: - - func - - field - - integer - - number - - regex - - wildcard - alias: - description: >- - Alias overrides the field name in the returned response. Applies only - if type is `func` - type: string - args: - description: Args are the arguments to the function - type: array - items: - $ref: '#/components/schemas/Field' - QueryConfig: - type: object - required: - - database - - measurement - - retentionPolicy - - areTagsAccepted - - tags - - groupBy - - fields - properties: - id: - type: string - database: - type: string - measurement: - type: string - retentionPolicy: - type: string - areTagsAccepted: - type: boolean - rawText: - type: string - tags: - type: object - groupBy: - type: object - properties: - time: - type: string - tags: - type: array - items: - type: string - required: - - time - - tags - fields: - type: array - items: - $ref: '#/components/schemas/Field' - range: - type: object - properties: - lower: - type: string - upper: - type: string - required: - - lower - - upper - DashboardQuery: - type: object - required: - - query - properties: - label: - type: string - description: Optional Y-axis user-facing label - range: - description: Optional default range of the Y-axis - type: object - required: - - upper - - lower - properties: - upper: - description: Upper bound of the display range of the Y-axis - type: integer - format: int64 - lower: - description: Lower bound of the display range of the Y-axis - type: integer - format: int64 - query: - type: string - source: - type: string - format: url - description: Optional URI for data source for this query - queryConfig: - $ref: '#/components/schemas/QueryConfig' - name: - type: string - description: An optional word or phrase that refers to the query - Axis: - type: object - description: A description of a particular axis for a visualization - properties: - bounds: - type: array - minItems: 0 - maxItems: 2 - description: >- - The extents of an axis in the form [lower, upper]. Clients determine - whether bounds are to be inclusive or exclusive of their limits - items: - type: integer - format: int64 - label: - description: label is a description of this Axis - type: string - prefix: - description: Prefix represents a label prefix for formatting axis values. - type: string - suffix: - description: Suffix represents a label suffix for formatting axis values. - type: string - base: - description: Base represents the radix for formatting axis values. - type: string - scale: - description: 'Scale is the axis formatting scale. Supported: "log", "linear"' - type: string - DashboardColor: - type: object - description: Color defines an encoding of data value into color space - properties: - id: - description: ID is the unique id of the cell color - type: string - type: - description: Type is how the color is used. - type: string - enum: - - min - - max - - threshold - hex: - description: Hex is the hex number of the color - type: string - maxLength: 7 - minLength: 7 - name: - description: Name is the user-facing name of the hex color - type: string - value: - description: Value is the data value mapped to this color - type: number - format: float - RenamableField: - description: Describes a field that can be renamed and made visible or invisible - type: object - properties: - internalName: - description: This is the calculated name of a field - readOnly: true - type: string - displayName: - description: This is the name that a field is renamed to by the user - type: string - visible: - description: Indicates whether this field should be visible on the table - type: boolean - V1Visualization: - properties: - type: - type: string - enum: ["chronograf-v1"] - queries: - type: array - items: - $ref: "#/components/schemas/DashboardQuery" - axes: - description: The viewport for a Cell's visualizations - type: object - properties: - x: - $ref: '#/components/schemas/Axis' - y: - $ref: '#/components/schemas/Axis' - y2: - $ref: '#/components/schemas/Axis' - graphType: - description: The viewport for a cell's graph/visualization - type: string - enum: - - single-stat - - line - - line-plus-single-stat - - line-stacked - - line-stepplot - - bar - - gauge - - table - default: line - colors: - description: Colors define color encoding of data into a visualization - type: array - items: - $ref: "#/components/schemas/DashboardColor" - legend: - description: Legend define encoding of data into a cell's legend - type: object - properties: - type: - description: type is the style of the legend - type: string - enum: - - static - orientation: - description: >- - orientation is the location of the legend with respect to the cell - graph - type: string - enum: - - top - - bottom - - left - - right - tableOptions: - properties: - verticalTimeAxis: - description: >- - verticalTimeAxis describes the orientation of the table by - indicating whether the time axis will be displayed vertically - type: boolean - sortBy: - $ref: "#/components/schemas/RenamableField" - wrapping: - description: wrapping describes the text wrapping style to be used in table cells - type: string - enum: - - truncate - - wrap - - single-line - fixFirstColumn: - description: >- - fixFirstColumn indicates whether the first column of the table - should be locked - type: boolean - fieldOptions: - description: >- - fieldOptions represent the fields retrieved by the query with - customization options - type: array - items: - $ref: '#/components/schemas/RenamableField' - timeFormat: - description: >- - timeFormat describes the display format for time values according to - moment.js date formatting - type: string - decimalPoints: - description: >- - decimal points indicates whether and how many digits to show after - decimal point - type: object - properties: - isEnforced: - description: Indicates whether decimal point setting should be enforced - type: boolean - digits: - description: The number of digists after decimal to display - type: integer - EmptyVisualization: - properties: - type: - type: string - enum: ["empty"] - Cell: - properties: - links: - $ref: "#/components/schemas/Links" - id: - readOnly: true - type: string - name: - type: string - visualization: - oneOf: - - $ref: "#/components/schemas/V1Visualization" - - $ref: "#/components/schemas/EmptyVisualization" - Cells: - type: object - properties: - links: - $ref: "#/components/schemas/Links" - cells: - type: array - items: - $ref: "#/components/schemas/Cell" - DashboardCell: - type: object - properties: - x: - type: integer - format: int32 - y: - type: integer - format: int32 - w: - type: integer - format: int32 - h: - type: integer - format: int32 - ref: - type: string - description: The reference to a cell from the cells API - Dashboard: - properties: - links: - $ref: "#/components/schemas/Links" - id: - readOnly: true - type: string - name: - type: string - cells: - type: array - items: - $ref: "#/components/schemas/DashboardCell" - Dashboards: - type: object - properties: - links: - $ref: "#/components/schemas/Links" - dashboards: - type: array - items: - $ref: "#/components/schemas/Dashboards" - Error: - properties: - code: - readOnly: true - type: integer - format: int32 - message: - readOnly: true - type: string - required: [code, message] diff --git a/chronograf/server/templates.go b/chronograf/server/templates.go deleted file mode 100644 index 8646c5d89a8..00000000000 --- a/chronograf/server/templates.go +++ /dev/null @@ -1,252 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - idgen "github.com/influxdata/influxdb/v2/chronograf/id" -) - -// ValidTemplateRequest checks if the request sent to the server is the correct format. -func ValidTemplateRequest(template *chronograf.Template) error { - switch template.Type { - default: - return fmt.Errorf("unknown template type %s", template.Type) - case "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases", "map", "influxql", "text": - } - - for _, v := range template.Values { - switch v.Type { - default: - return fmt.Errorf("unknown template variable type %s", v.Type) - case "csv", "map", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant", "influxql": - } - - if template.Type == "map" && v.Key == "" { - return fmt.Errorf("templates of type 'map' require a 'key'") - } - } - - if template.Type == "influxql" && template.Query == nil { - return fmt.Errorf("no query set for template of type 'influxql'") - } - - return nil -} - -type templateLinks struct { - Self string `json:"self"` // Self link mapping to this resource -} - -type templateResponse struct { - chronograf.Template - Links templateLinks `json:"links"` -} - -func newTemplateResponses(dID chronograf.DashboardID, tmps []chronograf.Template) []templateResponse { - res := make([]templateResponse, len(tmps)) - for i, t := range tmps { - res[i] = newTemplateResponse(dID, t) - } - return res -} - -type templatesResponses struct { - Templates []templateResponse `json:"templates"` -} - -func newTemplateResponse(dID chronograf.DashboardID, tmp chronograf.Template) templateResponse { - base := "/chronograf/v1/dashboards" - return templateResponse{ - Template: tmp, - Links: templateLinks{ - Self: fmt.Sprintf("%s/%d/templates/%s", base, dID, tmp.ID), - }, - } -} - -// Templates returns all templates from a dashboard within the store -func (s *Service) Templates(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - d, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - res := templatesResponses{ - Templates: newTemplateResponses(chronograf.DashboardID(id), d.Templates), - } - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// NewTemplate adds a template to an existing dashboard -func (s *Service) NewTemplate(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - dash, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - var template chronograf.Template - if err := json.NewDecoder(r.Body).Decode(&template); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := ValidTemplateRequest(&template); err != nil { - invalidData(w, err, s.Logger) - return - } - - ids := idgen.UUID{} - tid, err := ids.Generate() - if err != nil { - msg := fmt.Sprintf("Error creating template ID for dashboard %d: %v", id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - template.ID = chronograf.TemplateID(tid) - - dash.Templates = append(dash.Templates, template) - if err := s.Store.Dashboards(ctx).Update(ctx, dash); err != nil { - msg := fmt.Sprintf("Error adding template %s to dashboard %d: %v", tid, id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - res := newTemplateResponse(dash.ID, template) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// TemplateID retrieves a specific template from a dashboard -func (s *Service) TemplateID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - dash, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - tid := httprouter.GetParamFromContext(ctx, "tid") - for _, t := range dash.Templates { - if t.ID == chronograf.TemplateID(tid) { - res := newTemplateResponse(chronograf.DashboardID(id), t) - encodeJSON(w, http.StatusOK, res, s.Logger) - return - } - } - - notFound(w, id, s.Logger) -} - -// RemoveTemplate removes a specific template from an existing dashboard -func (s *Service) RemoveTemplate(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - dash, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - tid := httprouter.GetParamFromContext(ctx, "tid") - pos := -1 - for i, t := range dash.Templates { - if t.ID == chronograf.TemplateID(tid) { - pos = i - break - } - } - if pos == -1 { - notFound(w, id, s.Logger) - return - } - - dash.Templates = append(dash.Templates[:pos], dash.Templates[pos+1:]...) - if err := s.Store.Dashboards(ctx).Update(ctx, dash); err != nil { - msg := fmt.Sprintf("Error removing template %s from dashboard %d: %v", tid, id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -// ReplaceTemplate replaces a template entirely within an existing dashboard -func (s *Service) ReplaceTemplate(w http.ResponseWriter, r *http.Request) { - id, err := paramID("id", r) - if err != nil { - Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger) - return - } - - ctx := r.Context() - dash, err := s.Store.Dashboards(ctx).Get(ctx, chronograf.DashboardID(id)) - if err != nil { - notFound(w, id, s.Logger) - return - } - - tid := httprouter.GetParamFromContext(ctx, "tid") - pos := -1 - for i, t := range dash.Templates { - if t.ID == chronograf.TemplateID(tid) { - pos = i - break - } - } - if pos == -1 { - notFound(w, id, s.Logger) - return - } - - var template chronograf.Template - if err := json.NewDecoder(r.Body).Decode(&template); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := ValidTemplateRequest(&template); err != nil { - invalidData(w, err, s.Logger) - return - } - template.ID = chronograf.TemplateID(tid) - - dash.Templates[pos] = template - if err := s.Store.Dashboards(ctx).Update(ctx, dash); err != nil { - msg := fmt.Sprintf("Error updating template %s in dashboard %d: %v", tid, id, err) - Error(w, http.StatusInternalServerError, msg, s.Logger) - return - } - - res := newTemplateResponse(chronograf.DashboardID(id), template) - encodeJSON(w, http.StatusOK, res, s.Logger) -} diff --git a/chronograf/server/templates_test.go b/chronograf/server/templates_test.go deleted file mode 100644 index 8b0c8711178..00000000000 --- a/chronograf/server/templates_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package server - -import ( - "testing" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -func TestValidTemplateRequest(t *testing.T) { - tests := []struct { - name string - template *chronograf.Template - wantErr bool - }{ - { - name: "Valid Template", - template: &chronograf.Template{ - Type: "fieldKeys", - TemplateVar: chronograf.TemplateVar{ - Values: []chronograf.TemplateValue{ - { - Type: "fieldKey", - }, - }, - }, - }, - }, - { - name: "Invalid Template Type", - wantErr: true, - template: &chronograf.Template{ - Type: "Unknown Type", - TemplateVar: chronograf.TemplateVar{ - Values: []chronograf.TemplateValue{ - { - Type: "fieldKey", - }, - }, - }, - }, - }, - { - name: "Invalid Template Variable Type", - wantErr: true, - template: &chronograf.Template{ - Type: "csv", - TemplateVar: chronograf.TemplateVar{ - Values: []chronograf.TemplateValue{ - { - Type: "unknown value", - }, - }, - }, - }, - }, - { - name: "No query set", - wantErr: true, - template: &chronograf.Template{ - Type: "influxql", - }, - }, - { - name: "Valid Map type", - template: &chronograf.Template{ - Type: "map", - TemplateVar: chronograf.TemplateVar{ - Values: []chronograf.TemplateValue{ - { - Key: "key", - Value: "value", - Type: "map", - }, - }, - }, - }, - }, - { - name: "Map without Key", - wantErr: true, - template: &chronograf.Template{ - Type: "map", - TemplateVar: chronograf.TemplateVar{ - Values: []chronograf.TemplateValue{ - { - Value: "value", - Type: "map", - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := ValidTemplateRequest(tt.template); (err != nil) != tt.wantErr { - t.Errorf("ValidTemplateRequest() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/chronograf/server/test_helpers.go b/chronograf/server/test_helpers.go deleted file mode 100644 index e53c953aad3..00000000000 --- a/chronograf/server/test_helpers.go +++ /dev/null @@ -1,20 +0,0 @@ -package server - -import ( - "encoding/json" - - "github.com/google/go-cmp/cmp" -) - -func jsonEqual(s1, s2 string) (eq bool, err error) { - var o1, o2 interface{} - - if err = json.Unmarshal([]byte(s1), &o1); err != nil { - return - } - if err = json.Unmarshal([]byte(s2), &o2); err != nil { - return - } - - return cmp.Equal(o1, o2), nil -} diff --git a/chronograf/server/url_prefixer.go b/chronograf/server/url_prefixer.go deleted file mode 100644 index 1fd298c4698..00000000000 --- a/chronograf/server/url_prefixer.go +++ /dev/null @@ -1,192 +0,0 @@ -package server - -import ( - "bufio" - "bytes" - "io" - "net/http" - "regexp" - - "github.com/influxdata/influxdb/v2/chronograf" -) - -const ( - ErrNotFlusher = "Expected http.ResponseWriter to be an http.Flusher, but wasn't" -) - -// URLPrefixer is a wrapper for an http.Handler that will prefix all occurrences of a relative URL with the configured Prefix -type URLPrefixer struct { - Prefix string // the prefix to be appended after any detected Attrs - Next http.Handler // the http.Handler which will generate the content to be modified by this handler - Attrs [][]byte // a list of attrs that should have their URLs prefixed. For example `src="` or `href="` would be valid - Logger chronograf.Logger // The logger where prefixing errors will be dispatched to -} - -type wrapResponseWriter struct { - http.ResponseWriter - Substitute *io.PipeWriter - - headerWritten bool - dupHeader *http.Header -} - -func (wrw *wrapResponseWriter) Write(p []byte) (int, error) { - return wrw.Substitute.Write(p) -} - -func (wrw *wrapResponseWriter) WriteHeader(code int) { - if !wrw.headerWritten { - wrw.ResponseWriter.Header().Set("Content-Type", wrw.dupHeader.Get("Content-Type")) - header := wrw.ResponseWriter.Header() - // Filter out content length header to prevent stopping writing - if wrw.dupHeader != nil { - for k, v := range *wrw.dupHeader { - if k == "Content-Length" { - continue - } - header[k] = v - } - } - - wrw.headerWritten = true - } - wrw.ResponseWriter.WriteHeader(code) -} - -// Header() copies the Header map from the underlying ResponseWriter to prevent -// modifications to it by callers -func (wrw *wrapResponseWriter) Header() http.Header { - if wrw.dupHeader == nil { - h := http.Header{} - origHeader := wrw.ResponseWriter.Header() - for k, v := range origHeader { - h[k] = v - } - wrw.dupHeader = &h - } - return *wrw.dupHeader -} - -// ChunkSize is the number of bytes per chunked transfer-encoding -const ChunkSize int = 512 - -// ServeHTTP implements an http.Handler that prefixes relative URLs from the -// Next handler with the configured prefix. It does this by examining the -// stream through the ResponseWriter, and appending the Prefix after any of the -// Attrs detected in the stream. -func (up *URLPrefixer) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - // extract the flusher for flushing chunks - flusher, ok := rw.(http.Flusher) - - if !ok { - up.Logger.Info(ErrNotFlusher) - up.Next.ServeHTTP(rw, r) - return - } - - isSVG, _ := regexp.Match(".svg$", []byte(r.URL.String())) - if isSVG { - up.Next.ServeHTTP(rw, r) - return - } - - // chunked transfer because we're modifying the response on the fly, so we - // won't know the final content-length - rw.Header().Set("Connection", "Keep-Alive") - rw.Header().Set("Transfer-Encoding", "chunked") - - writtenCount := 0 // number of bytes written to rw - nextRead, nextWrite := io.Pipe() - go func() { - defer nextWrite.Close() - up.Next.ServeHTTP(&wrapResponseWriter{ResponseWriter: rw, Substitute: nextWrite}, r) - }() - - // setup a buffer which is the max length of our target attrs - b := make([]byte, up.maxlen(up.Attrs...)) - io.ReadFull(nextRead, b) // prime the buffer with the start of the input - buf := bytes.NewBuffer(b) - - // Read next handler's response byte by byte - src := bufio.NewScanner(nextRead) - src.Split(bufio.ScanBytes) - for { - window := buf.Bytes() - - // advance a byte if window is not a src attr - if matchlen, match := up.match(window, up.Attrs...); matchlen == 0 { - if src.Scan() { - // shift the next byte into buf - rw.Write(buf.Next(1)) - writtenCount++ - buf.Write(src.Bytes()) - - if writtenCount >= ChunkSize { - flusher.Flush() - writtenCount = 0 - } - } else { - if err := src.Err(); err != nil { - up.Logger. - WithField("component", "prefixer"). - Error("Error encountered while scanning: err:", err) - } - rw.Write(window) - flusher.Flush() - break - } - continue - } else { - buf.Next(matchlen) // advance to the relative URL - for i := 0; i < matchlen; i++ { - src.Scan() - buf.Write(src.Bytes()) - } - rw.Write(match) // add the src attr to the output - io.WriteString(rw, up.Prefix) // write the prefix - } - } -} - -// match compares the subject against a list of targets. If there is a match -// between any of them a non-zero value is returned. The returned value is the -// length of the match. It is assumed that subject's length > length of all -// targets. The matching []byte is also returned as the second return parameter -func (up *URLPrefixer) match(subject []byte, targets ...[]byte) (int, []byte) { - for _, target := range targets { - if bytes.Equal(subject[:len(target)], target) { - return len(target), target - } - } - return 0, []byte{} -} - -// maxlen returns the length of the largest []byte provided to it as an argument -func (up *URLPrefixer) maxlen(targets ...[]byte) int { - max := 0 - for _, tgt := range targets { - if tlen := len(tgt); tlen > max { - max = tlen - } - } - return max -} - -// NewDefaultURLPrefixer returns a URLPrefixer that will prefix any src and -// href attributes found in HTML as well as any url() directives found in CSS -// with the provided prefix. Additionally, it will prefix any `data-basepath` -// attributes as well for informing front end logic about any prefixes. `next` -// is the next http.Handler that will have its output prefixed -func NewDefaultURLPrefixer(prefix string, next http.Handler, lg chronograf.Logger) *URLPrefixer { - return &URLPrefixer{ - Prefix: prefix, - Next: next, - Logger: lg, - Attrs: [][]byte{ - []byte(`src="`), - []byte(`href="`), - []byte(`url(`), - []byte(`data-basepath="`), // for forwarding basepath to frontend - }, - } -} diff --git a/chronograf/server/url_prefixer_test.go b/chronograf/server/url_prefixer_test.go deleted file mode 100644 index 48fb68b92eb..00000000000 --- a/chronograf/server/url_prefixer_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package server_test - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/server" -) - -var prefixerTests = []struct { - name string - subject string - expected string - shouldErr bool - attrs [][]byte -}{ - { - `One script tag`, - ` - `, - ` - - - - Chronograf - - -
- - `, - false, - [][]byte{ - []byte(`src="`), - []byte(`href="`), - }, - }, -} - -func Test_Server_Prefixer_RewritesURLs(t *testing.T) { - t.Parallel() - - for _, test := range prefixerTests { - subject := test.subject - expected := test.expected - - backend := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, subject) - }) - - pfx := &server.URLPrefixer{Prefix: "/arbitraryprefix", Next: backend, Attrs: test.attrs} - - ts := httptest.NewServer(pfx) - defer ts.Close() - - res, err := http.Get(ts.URL) - if err != nil { - t.Error("Unexpected error fetching from prefixer: err:", err) - } - - actual, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Error("Unable to read prefixed body: err:", err) - } - - if string(actual) != expected+"\n" { - t.Error(test.name, ":\n Unsuccessful prefixing.\n\tWant:", fmt.Sprintf("%+q", expected), "\n\tGot: ", fmt.Sprintf("%+q", string(actual))) - } - } -} - -// clogger is an http.ResponseWriter that is not an http.Flusher. It is used -// for testing the behavior of handlers that may rely on specific behavior of -// http.Flusher -type clogger struct { - next http.ResponseWriter -} - -func (c *clogger) Header() http.Header { - return c.next.Header() -} - -func (c *clogger) Write(bytes []byte) (int, error) { - return c.next.Write(bytes) -} - -func (c *clogger) WriteHeader(code int) { - c.next.WriteHeader(code) -} - -func Test_Server_Prefixer_NoPrefixingWithoutFlusther(t *testing.T) { - backend := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - fmt.Fprintf(rw, "Hill Valley Preservation Society") - }) - - wrapFunc := func(next http.Handler) http.Handler { - return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - clog := &clogger{rw} - next.ServeHTTP(clog, r) - }) - } - - tl := &mocks.TestLogger{} - pfx := &server.URLPrefixer{ - Prefix: "/hill", - Next: backend, - Logger: tl, - Attrs: [][]byte{ - []byte("href=\""), - }, - } - - ts := httptest.NewServer(wrapFunc(pfx)) - defer ts.Close() - - res, err := http.Get(ts.URL) - if err != nil { - t.Fatal("Unexpected error fetching from prefixer: err:", err) - } - - actual, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal("Unable to read prefixed body: err:", err) - } - - unexpected := "Hill Valley Preservation Society" - expected := "Hill Valley Preservation Society" - if string(actual) == unexpected { - t.Error("No Flusher", ":\n Prefixing occurred without an http.Flusher") - } - - if string(actual) != expected { - t.Error("No Flusher", ":\n\tPrefixing failed to output without an http.Flusher\n\t\tWant:\n", expected, "\n\t\tGot:\n", string(actual)) - } - - if !tl.HasMessage("info", server.ErrNotFlusher) { - t.Error("No Flusher", ":\n Expected Error Message: \"", server.ErrNotFlusher, "\" but saw none. Msgs:", tl.Messages) - } -} diff --git a/chronograf/server/users.go b/chronograf/server/users.go deleted file mode 100644 index e9cb286604f..00000000000 --- a/chronograf/server/users.go +++ /dev/null @@ -1,379 +0,0 @@ -package server - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "sort" - "strconv" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -type userRequest struct { - ID uint64 `json:"id,string"` - Name string `json:"name"` - Provider string `json:"provider"` - Scheme string `json:"scheme"` - SuperAdmin bool `json:"superAdmin"` - Roles []chronograf.Role `json:"roles"` -} - -func (r *userRequest) ValidCreate() error { - if r.Name == "" { - return fmt.Errorf("name required on Chronograf User request body") - } - if r.Provider == "" { - return fmt.Errorf("provider required on Chronograf User request body") - } - if r.Scheme == "" { - return fmt.Errorf("scheme required on Chronograf User request body") - } - - // TODO: This Scheme value is hard-coded temporarily since we only currently - // support OAuth2. This hard-coding should be removed whenever we add - // support for other authentication schemes. - r.Scheme = "oauth2" - return r.ValidRoles() -} - -func (r *userRequest) ValidUpdate() error { - if r.Roles == nil { - return fmt.Errorf("no Roles to update") - } - return r.ValidRoles() -} - -func (r *userRequest) ValidRoles() error { - if len(r.Roles) > 0 { - orgs := map[string]bool{} - for _, r := range r.Roles { - if r.Organization == "" { - return fmt.Errorf("no organization was provided") - } - if _, ok := orgs[r.Organization]; ok { - return fmt.Errorf("duplicate organization %q in roles", r.Organization) - } - orgs[r.Organization] = true - switch r.Name { - case roles.MemberRoleName, roles.ViewerRoleName, roles.EditorRoleName, roles.AdminRoleName, roles.WildcardRoleName: - continue - default: - return fmt.Errorf("unknown role %s. Valid roles are 'member', 'viewer', 'editor', 'admin', and '*'", r.Name) - } - } - } - return nil -} - -type userResponse struct { - Links selfLinks `json:"links"` - ID uint64 `json:"id,string"` - Name string `json:"name"` - Provider string `json:"provider"` - Scheme string `json:"scheme"` - SuperAdmin bool `json:"superAdmin"` - Roles []chronograf.Role `json:"roles"` -} - -func newUserResponse(u *chronograf.User, org string) *userResponse { - // This ensures that any user response with no roles returns an empty array instead of - // null when marshaled into JSON. That way, JavaScript doesn't need any guard on the - // key existing and it can simply be iterated over. - if u.Roles == nil { - u.Roles = []chronograf.Role{} - } - var selfLink string - if org != "" { - selfLink = fmt.Sprintf("/chronograf/v1/organizations/%s/users/%d", org, u.ID) - } else { - selfLink = fmt.Sprintf("/chronograf/v1/users/%d", u.ID) - } - return &userResponse{ - ID: u.ID, - Name: u.Name, - Provider: u.Provider, - Scheme: u.Scheme, - Roles: u.Roles, - SuperAdmin: u.SuperAdmin, - Links: selfLinks{ - Self: selfLink, - }, - } -} - -type usersResponse struct { - Links selfLinks `json:"links"` - Users []*userResponse `json:"users"` -} - -func newUsersResponse(users []chronograf.User, org string) *usersResponse { - usersResp := make([]*userResponse, len(users)) - for i, user := range users { - usersResp[i] = newUserResponse(&user, org) - } - sort.Slice(usersResp, func(i, j int) bool { - return usersResp[i].ID < usersResp[j].ID - }) - - var selfLink string - if org != "" { - selfLink = fmt.Sprintf("/chronograf/v1/organizations/%s/users", org) - } else { - selfLink = "/chronograf/v1/users" - } - return &usersResponse{ - Users: usersResp, - Links: selfLinks{ - Self: selfLink, - }, - } -} - -// UserID retrieves a Chronograf user with ID from store -func (s *Service) UserID(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - idStr := httprouter.GetParamFromContext(ctx, "id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil { - Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger) - return - } - user, err := s.Store.Users(ctx).Get(ctx, chronograf.UserQuery{ID: &id}) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - orgID := httprouter.GetParamFromContext(ctx, "oid") - res := newUserResponse(user, orgID) - location(w, res.Links.Self) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -// NewUser adds a new Chronograf user to store -func (s *Service) NewUser(w http.ResponseWriter, r *http.Request) { - var req userRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - if err := req.ValidCreate(); err != nil { - invalidData(w, err, s.Logger) - return - } - - ctx := r.Context() - - serverCtx := serverContext(ctx) - cfg, err := s.Store.Config(serverCtx).Get(serverCtx) - if err != nil { - Error(w, http.StatusInternalServerError, err.Error(), s.Logger) - return - } - - if err := s.validRoles(serverCtx, req.Roles); err != nil { - invalidData(w, err, s.Logger) - return - } - - user := &chronograf.User{ - Name: req.Name, - Provider: req.Provider, - Scheme: req.Scheme, - Roles: req.Roles, - } - - if cfg.Auth.SuperAdminNewUsers { - req.SuperAdmin = true - } - - if err := setSuperAdmin(ctx, req, user); err != nil { - Error(w, http.StatusUnauthorized, err.Error(), s.Logger) - return - } - - res, err := s.Store.Users(ctx).Add(ctx, user) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - orgID := httprouter.GetParamFromContext(ctx, "oid") - cu := newUserResponse(res, orgID) - location(w, cu.Links.Self) - encodeJSON(w, http.StatusCreated, cu, s.Logger) -} - -// RemoveUser deletes a Chronograf user from store -func (s *Service) RemoveUser(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - idStr := httprouter.GetParamFromContext(ctx, "id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil { - Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger) - return - } - - u, err := s.Store.Users(ctx).Get(ctx, chronograf.UserQuery{ID: &id}) - if err != nil { - Error(w, http.StatusNotFound, err.Error(), s.Logger) - return - } - if err := s.Store.Users(ctx).Delete(ctx, u); err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - w.WriteHeader(http.StatusNoContent) -} - -// UpdateUser updates a Chronograf user in store -func (s *Service) UpdateUser(w http.ResponseWriter, r *http.Request) { - var req userRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - invalidJSON(w, s.Logger) - return - } - - ctx := r.Context() - idStr := httprouter.GetParamFromContext(ctx, "id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil { - Error(w, http.StatusBadRequest, fmt.Sprintf("invalid user id: %s", err.Error()), s.Logger) - return - } - - if err := req.ValidUpdate(); err != nil { - invalidData(w, err, s.Logger) - return - } - - u, err := s.Store.Users(ctx).Get(ctx, chronograf.UserQuery{ID: &id}) - if err != nil { - Error(w, http.StatusNotFound, err.Error(), s.Logger) - return - } - - serverCtx := serverContext(ctx) - if err := s.validRoles(serverCtx, req.Roles); err != nil { - invalidData(w, err, s.Logger) - return - } - - // ValidUpdate should ensure that req.Roles is not nil - u.Roles = req.Roles - - // If the request contains a name, it must be the same as the - // one on the user. This is particularly useful to the front-end - // because they would like to provide the whole user object, - // including the name, provider, and scheme in update requests. - // But currently, it is not possible to change name, provider, or - // scheme via the API. - if req.Name != "" && req.Name != u.Name { - err := fmt.Errorf("cannot update Name") - invalidData(w, err, s.Logger) - return - } - if req.Provider != "" && req.Provider != u.Provider { - err := fmt.Errorf("cannot update Provider") - invalidData(w, err, s.Logger) - return - } - if req.Scheme != "" && req.Scheme != u.Scheme { - err := fmt.Errorf("cannot update Scheme") - invalidData(w, err, s.Logger) - return - } - - // Don't allow SuperAdmins to modify their own SuperAdmin status. - // Allowing them to do so could result in an application where there - // are no super admins. - ctxUser, ok := hasUserContext(ctx) - if !ok { - Error(w, http.StatusInternalServerError, "failed to retrieve user from context", s.Logger) - return - } - // If the user being updated is the user making the request and they are - // changing their SuperAdmin status, return an unauthorized error - if ctxUser.ID == u.ID && u.SuperAdmin && !req.SuperAdmin { - Error(w, http.StatusUnauthorized, "user cannot modify their own SuperAdmin status", s.Logger) - return - } - - if err := setSuperAdmin(ctx, req, u); err != nil { - Error(w, http.StatusUnauthorized, err.Error(), s.Logger) - return - } - - err = s.Store.Users(ctx).Update(ctx, u) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - orgID := httprouter.GetParamFromContext(ctx, "oid") - cu := newUserResponse(u, orgID) - location(w, cu.Links.Self) - encodeJSON(w, http.StatusOK, cu, s.Logger) -} - -// Users retrieves all Chronograf users from store -func (s *Service) Users(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - users, err := s.Store.Users(ctx).All(ctx) - if err != nil { - Error(w, http.StatusBadRequest, err.Error(), s.Logger) - return - } - - orgID := httprouter.GetParamFromContext(ctx, "oid") - res := newUsersResponse(users, orgID) - encodeJSON(w, http.StatusOK, res, s.Logger) -} - -func setSuperAdmin(ctx context.Context, req userRequest, user *chronograf.User) error { - // At a high level, this function checks the following - // 1. Is the user making the request a SuperAdmin. - // If they are, allow them to make whatever changes they please. - // - // 2. Is the user making the request trying to change the SuperAdmin - // status. If so, return an error. - // - // 3. If none of the above are the case, let the user make whichever - // changes were requested. - - // Only allow users to set SuperAdmin if they have the superadmin context - // TODO(desa): Refactor this https://github.com/influxdata/influxdb/chronograf/issues/2207 - if isSuperAdmin := hasSuperAdminContext(ctx); isSuperAdmin { - user.SuperAdmin = req.SuperAdmin - } else if !isSuperAdmin && (user.SuperAdmin != req.SuperAdmin) { - // If req.SuperAdmin has been set, and the request was not made with the SuperAdmin - // context, return error - return fmt.Errorf("user does not have authorization required to set SuperAdmin status. See https://github.com/influxdata/influxdb/chronograf/issues/2601 for more information") - } - - return nil -} - -func (s *Service) validRoles(ctx context.Context, rs []chronograf.Role) error { - for i, role := range rs { - // verify that the organization exists - org, err := s.Store.Organizations(ctx).Get(ctx, chronograf.OrganizationQuery{ID: &role.Organization}) - if err != nil { - return err - } - if role.Name == roles.WildcardRoleName { - role.Name = org.DefaultRole - rs[i] = role - } - } - - return nil -} diff --git a/chronograf/server/users_test.go b/chronograf/server/users_test.go deleted file mode 100644 index 9bd745a1cd8..00000000000 --- a/chronograf/server/users_test.go +++ /dev/null @@ -1,1771 +0,0 @@ -package server - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/bouk/httprouter" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/mocks" - "github.com/influxdata/influxdb/v2/chronograf/roles" -) - -func TestService_UserID(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - id string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Get Single Chronograf User", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1337: - return &chronograf.User{ - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - roles.ViewerRole, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - id: "1337", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"id":"1337","superAdmin":false,"name":"billysteve","provider":"google","scheme":"oauth2","links":{"self":"/chronograf/v1/users/1337"},"roles":[{"name":"viewer"}]}`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - UsersStore: tt.fields.UsersStore, - }, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(httprouter.WithParams( - context.Background(), - httprouter.Params{ - { - Key: "id", - Value: tt.id, - }, - })) - - s.UserID(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. UserID() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. UserID() = %v, want %v", tt.name, content, tt.wantContentType) - } - if eq, _ := jsonEqual(string(body), tt.wantBody); tt.wantBody != "" && !eq { - t.Errorf("%q. UserID() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - }) - } -} - -func TestService_NewUser(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - OrganizationsStore chronograf.OrganizationsStore - ConfigStore chronograf.ConfigStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - user *userRequest - userKeyUser *chronograf.User - } - tests := []struct { - name string - fields fields - args args - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Create a new Chronograf User", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://any.url", - nil, - ), - user: &userRequest{ - Name: "bob", - Provider: "github", - Scheme: "oauth2", - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, user *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1338, - Name: "bob", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, nil - }, - }, - }, - wantStatus: http.StatusCreated, - wantContentType: "application/json", - wantBody: `{"id":"1338","superAdmin":false,"name":"bob","provider":"github","scheme":"oauth2","roles":[],"links":{"self":"/chronograf/v1/users/1338"}}`, - }, - { - name: "Create a new Chronograf User with multiple roles", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://any.url", - nil, - ), - user: &userRequest{ - Name: "bob", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - { - Name: roles.ViewerRoleName, - Organization: "2", - }, - }, - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "org", - DefaultRole: roles.ViewerRoleName, - }, nil - case "2": - return &chronograf.Organization{ - ID: "2", - Name: "another", - DefaultRole: roles.MemberRoleName, - }, nil - } - return nil, fmt.Errorf("org not found") - }, - }, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, user *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1338, - Name: "bob", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - { - Name: roles.ViewerRoleName, - Organization: "2", - }, - }, - }, nil - }, - }, - }, - wantStatus: http.StatusCreated, - wantContentType: "application/json", - wantBody: `{"id":"1338","superAdmin":false,"name":"bob","provider":"github","scheme":"oauth2","roles":[{"name":"admin","organization":"1"},{"name":"viewer","organization":"2"}],"links":{"self":"/chronograf/v1/users/1338"}}`, - }, - { - name: "Create a new Chronograf User with multiple roles same org", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://any.url", - nil, - ), - user: &userRequest{ - Name: "bob", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - { - Name: roles.ViewerRoleName, - Organization: "1", - }, - }, - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, user *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1338, - Name: "bob", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - { - Name: roles.ViewerRoleName, - Organization: "1", - }, - }, - }, nil - }, - }, - }, - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"duplicate organization \"1\" in roles"}`, - }, - { - name: "Create a new SuperAdmin User - Not as superadmin", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://any.url", - nil, - ), - user: &userRequest{ - Name: "bob", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - }, - userKeyUser: &chronograf.User{ - ID: 0, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: false, - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, user *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1338, - Name: "bob", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, nil - }, - }, - }, - wantStatus: http.StatusUnauthorized, - wantContentType: "application/json", - wantBody: `{"code":401,"message":"user does not have authorization required to set SuperAdmin status. See https://github.com/influxdata/influxdb/chronograf/issues/2601 for more information"}`, - }, - { - name: "Create a new SuperAdmin User - as superadmin", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://any.url", - nil, - ), - user: &userRequest{ - Name: "bob", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - }, - userKeyUser: &chronograf.User{ - ID: 0, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, user *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1338, - Name: "bob", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - SuperAdmin: true, - }, nil - }, - }, - }, - wantStatus: http.StatusCreated, - wantContentType: "application/json", - wantBody: `{"id":"1338","superAdmin":true,"name":"bob","provider":"github","scheme":"oauth2","roles":[],"links":{"self":"/chronograf/v1/users/1338"}}`, - }, - { - name: "Create a new User with SuperAdminNewUsers: true in ConfigStore", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://any.url", - nil, - ), - user: &userRequest{ - Name: "bob", - Provider: "github", - Scheme: "oauth2", - }, - userKeyUser: &chronograf.User{ - ID: 0, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: true, - }, - }, - }, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, user *chronograf.User) (*chronograf.User, error) { - user.ID = 1338 - return user, nil - }, - }, - }, - wantStatus: http.StatusCreated, - wantContentType: "application/json", - wantBody: `{"id":"1338","superAdmin":true,"name":"bob","provider":"github","scheme":"oauth2","roles":[],"links":{"self":"/chronograf/v1/users/1338"}}`, - }, - { - name: "Create a new Chronograf User with multiple roles with wildcard default role", - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "POST", - "http://any.url", - nil, - ), - user: &userRequest{ - Name: "bob", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - { - Name: roles.WildcardRoleName, - Organization: "2", - }, - }, - }, - }, - fields: fields{ - Logger: &chronograf.NoopLogger{}, - ConfigStore: &mocks.ConfigStore{ - Config: &chronograf.Config{ - Auth: chronograf.AuthConfig{ - SuperAdminNewUsers: false, - }, - }, - }, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "org", - DefaultRole: roles.ViewerRoleName, - }, nil - case "2": - return &chronograf.Organization{ - ID: "2", - Name: "another", - DefaultRole: roles.MemberRoleName, - }, nil - } - return nil, fmt.Errorf("org not found") - }, - }, - UsersStore: &mocks.UsersStore{ - AddF: func(ctx context.Context, user *chronograf.User) (*chronograf.User, error) { - return &chronograf.User{ - ID: 1338, - Name: "bob", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - { - Name: roles.MemberRoleName, - Organization: "2", - }, - }, - }, nil - }, - }, - }, - wantStatus: http.StatusCreated, - wantContentType: "application/json", - wantBody: `{"id":"1338","superAdmin":false,"name":"bob","provider":"github","scheme":"oauth2","roles":[{"name":"admin","organization":"1"},{"name":"member","organization":"2"}],"links":{"self":"/chronograf/v1/users/1338"}}`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - UsersStore: tt.fields.UsersStore, - ConfigStore: tt.fields.ConfigStore, - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: tt.fields.Logger, - } - - buf, _ := json.Marshal(tt.args.user) - tt.args.r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - - ctx := tt.args.r.Context() - if tt.args.userKeyUser != nil { - ctx = context.WithValue(ctx, UserContextKey, tt.args.userKeyUser) - } - - tt.args.r = tt.args.r.WithContext(ctx) - - s.NewUser(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. UserID() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. UserID() = %v, want %v", tt.name, content, tt.wantContentType) - } - if eq, _ := jsonEqual(string(body), tt.wantBody); tt.wantBody != "" && !eq { - t.Errorf("%q. UserID() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - }) - } -} - -func TestService_RemoveUser(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - user *chronograf.User - id string - } - tests := []struct { - name string - fields fields - args args - wantStatus int - wantBody string - }{ - { - name: "Delete a Chronograf User", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1339: - return &chronograf.User{ - ID: 1339, - Name: "helena", - Provider: "heroku", - Scheme: "oauth2", - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - DeleteF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "DELETE", - "http://any.url", - nil, - ), - user: &chronograf.User{ - ID: 1338, - Name: "helena", - Provider: "heroku", - Scheme: "oauth2", - }, - id: "1339", - }, - wantStatus: http.StatusNoContent, - }, - { - name: "Deleting yourself", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1339: - return &chronograf.User{ - ID: 1339, - Name: "helena", - Provider: "heroku", - Scheme: "oauth2", - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - DeleteF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "DELETE", - "http://any.url", - nil, - ), - user: &chronograf.User{ - ID: 1339, - Name: "helena", - Provider: "heroku", - Scheme: "oauth2", - }, - id: "1339", - }, - wantStatus: http.StatusNoContent, - wantBody: ``, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - UsersStore: tt.fields.UsersStore, - }, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(httprouter.WithParams( - context.Background(), - httprouter.Params{ - { - Key: "id", - Value: tt.args.id, - }, - }, - )) - - if tt.args.user != nil { - ctx := tt.args.r.Context() - ctx = context.WithValue(ctx, UserContextKey, tt.args.user) - tt.args.r = tt.args.r.WithContext(ctx) - } - - s.RemoveUser(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. RemoveUser() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantStatus == http.StatusNoContent { - return - } - if eq, _ := jsonEqual(string(body), tt.wantBody); !eq { - t.Errorf("%q. RemoveUser() = %v, want %v", tt.name, string(body), tt.wantBody) - } - }) - } -} - -func TestService_UpdateUser(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - OrganizationsStore chronograf.OrganizationsStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - user *userRequest - userKeyUser *chronograf.User - } - tests := []struct { - name string - fields fields - args args - id string - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Update a Chronograf user - no roles", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1336: - return &chronograf.User{ - ID: 1336, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil, - ), - userKeyUser: &chronograf.User{ - ID: 0, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: false, - }, - user: &userRequest{ - ID: 1336, - Roles: []chronograf.Role{}, - }, - }, - id: "1336", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"id":"1336","superAdmin":false,"name":"bobbetta","provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/1336"},"roles":[]}`, - }, - { - name: "Update a Chronograf user", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "org", - DefaultRole: roles.ViewerRoleName, - }, nil - } - return nil, fmt.Errorf("org not found") - }, - }, - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1336: - return &chronograf.User{ - ID: 1336, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil, - ), - userKeyUser: &chronograf.User{ - ID: 0, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: false, - }, - user: &userRequest{ - ID: 1336, - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - }, - }, - }, - id: "1336", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"id":"1336","superAdmin":false,"name":"bobbetta","provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/1336"},"roles":[{"name":"admin","organization":"1"}]}`, - }, - { - name: "Update a Chronograf user roles different orgs", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "org", - DefaultRole: roles.ViewerRoleName, - }, nil - case "2": - return &chronograf.Organization{ - ID: "2", - Name: "another", - DefaultRole: roles.ViewerRoleName, - }, nil - } - return nil, fmt.Errorf("org not found") - }, - }, - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1336: - return &chronograf.User{ - ID: 1336, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - roles.EditorRole, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil, - ), - userKeyUser: &chronograf.User{ - ID: 0, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: false, - }, - user: &userRequest{ - ID: 1336, - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - { - Name: roles.ViewerRoleName, - Organization: "2", - }, - }, - }, - }, - id: "1336", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"id":"1336","superAdmin":false,"name":"bobbetta","provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/1336"},"roles":[{"name":"admin","organization":"1"},{"name":"viewer","organization":"2"}]}`, - }, - { - name: "Update a Chronograf user roles same org", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1336: - return &chronograf.User{ - ID: 1336, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - roles.EditorRole, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil, - ), - user: &userRequest{ - ID: 1336, - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - { - Name: roles.ViewerRoleName, - Organization: "1", - }, - }, - }, - }, - id: "1336", - wantStatus: http.StatusUnprocessableEntity, - wantContentType: "application/json", - wantBody: `{"code":422,"message":"duplicate organization \"1\" in roles"}`, - }, - { - name: "SuperAdmin modifying their own SuperAdmin Status - user missing from context", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "org", - DefaultRole: roles.ViewerRoleName, - }, nil - } - return nil, fmt.Errorf("org not found") - }, - }, - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1336: - return &chronograf.User{ - ID: 1336, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil, - ), - user: &userRequest{ - ID: 1336, - SuperAdmin: false, - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - }, - }, - }, - id: "1336", - wantStatus: http.StatusInternalServerError, - wantContentType: "application/json", - wantBody: `{"code":500,"message":"failed to retrieve user from context"}`, - }, - { - name: "SuperAdmin modifying their own SuperAdmin Status", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "org", - DefaultRole: roles.ViewerRoleName, - }, nil - } - return nil, fmt.Errorf("org not found") - }, - }, - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1336: - return &chronograf.User{ - ID: 1336, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil, - ), - user: &userRequest{ - ID: 1336, - SuperAdmin: false, - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - }, - }, - userKeyUser: &chronograf.User{ - ID: 1336, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - }, - }, - id: "1336", - wantStatus: http.StatusUnauthorized, - wantContentType: "application/json", - wantBody: `{"code":401,"message":"user cannot modify their own SuperAdmin status"}`, - }, - { - name: "Update a SuperAdmin's Roles - without super admin context", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "org", - DefaultRole: roles.ViewerRoleName, - }, nil - } - return nil, fmt.Errorf("org not found") - }, - }, - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1336: - return &chronograf.User{ - ID: 1336, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil, - ), - user: &userRequest{ - ID: 1336, - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - }, - }, - userKeyUser: &chronograf.User{ - ID: 0, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: false, - }, - }, - id: "1336", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"links":{"self":"/chronograf/v1/users/1336"},"id":"1336","name":"bobbetta","provider":"github","scheme":"oauth2","superAdmin":true,"roles":[{"name":"admin","organization":"1"}]}`, - }, - { - name: "Update a Chronograf user to super admin - without super admin context", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "org", - DefaultRole: roles.ViewerRoleName, - }, nil - } - return nil, fmt.Errorf("org not found") - }, - }, - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1336: - return &chronograf.User{ - ID: 1336, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - roles.EditorRole, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil, - ), - user: &userRequest{ - ID: 1336, - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - }, - }, - userKeyUser: &chronograf.User{ - ID: 0, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: false, - }, - }, - id: "1336", - wantStatus: http.StatusUnauthorized, - wantContentType: "application/json", - wantBody: `{"code":401,"message":"user does not have authorization required to set SuperAdmin status. See https://github.com/influxdata/influxdb/chronograf/issues/2601 for more information"}`, - }, - { - name: "Update a Chronograf user to super admin - with super admin context", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - OrganizationsStore: &mocks.OrganizationsStore{ - GetF: func(ctx context.Context, q chronograf.OrganizationQuery) (*chronograf.Organization, error) { - switch *q.ID { - case "1": - return &chronograf.Organization{ - ID: "1", - Name: "org", - DefaultRole: roles.ViewerRoleName, - }, nil - } - return nil, fmt.Errorf("org not found") - }, - }, - UsersStore: &mocks.UsersStore{ - UpdateF: func(ctx context.Context, user *chronograf.User) error { - return nil - }, - GetF: func(ctx context.Context, q chronograf.UserQuery) (*chronograf.User, error) { - switch *q.ID { - case 1336: - return &chronograf.User{ - ID: 1336, - Name: "bobbetta", - Provider: "github", - Scheme: "oauth2", - Roles: []chronograf.Role{ - roles.EditorRole, - }, - }, nil - default: - return nil, fmt.Errorf("user with ID %d not found", *q.ID) - } - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "PATCH", - "http://any.url", - nil, - ), - user: &userRequest{ - ID: 1336, - SuperAdmin: true, - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "1", - }, - }, - }, - userKeyUser: &chronograf.User{ - ID: 0, - Name: "coolUser", - Provider: "github", - Scheme: "oauth2", - SuperAdmin: true, - }, - }, - id: "1336", - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"id":"1336","superAdmin":true,"name":"bobbetta","provider":"github","scheme":"oauth2","links":{"self":"/chronograf/v1/users/1336"},"roles":[{"name":"admin","organization":"1"}]}`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - UsersStore: tt.fields.UsersStore, - OrganizationsStore: tt.fields.OrganizationsStore, - }, - Logger: tt.fields.Logger, - } - - tt.args.r = tt.args.r.WithContext(httprouter.WithParams(context.Background(), - httprouter.Params{ - { - Key: "id", - Value: tt.id, - }, - })) - buf, _ := json.Marshal(tt.args.user) - tt.args.r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - - ctx := tt.args.r.Context() - if tt.args.userKeyUser != nil { - ctx = context.WithValue(ctx, UserContextKey, tt.args.userKeyUser) - } - - tt.args.r = tt.args.r.WithContext(ctx) - - s.UpdateUser(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. UpdateUser() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. UpdateUser() = %v, want %v", tt.name, content, tt.wantContentType) - } - if eq, _ := jsonEqual(string(body), tt.wantBody); tt.wantBody != "" && !eq { - t.Errorf("%q. UpdateUser()\ngot:%v\n,\nwant:%v", tt.name, string(body), tt.wantBody) - } - }) - } -} - -func TestService_Users(t *testing.T) { - type fields struct { - UsersStore chronograf.UsersStore - Logger chronograf.Logger - } - type args struct { - w *httptest.ResponseRecorder - r *http.Request - } - tests := []struct { - name string - fields fields - args args - wantStatus int - wantContentType string - wantBody string - }{ - { - name: "Get all Chronograf users", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - AllF: func(ctx context.Context) ([]chronograf.User, error) { - return []chronograf.User{ - { - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - roles.EditorRole, - }, - }, - { - ID: 1338, - Name: "bobbettastuhvetta", - Provider: "auth0", - Scheme: "oauth2", - }, - }, nil - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"users":[{"id":"1337","superAdmin":false,"name":"billysteve","provider":"google","scheme":"oauth2","roles":[{"name":"editor"}],"links":{"self":"/chronograf/v1/users/1337"}},{"id":"1338","superAdmin":false,"name":"bobbettastuhvetta","provider":"auth0","scheme":"oauth2","roles":[],"links":{"self":"/chronograf/v1/users/1338"}}],"links":{"self":"/chronograf/v1/users"}}`, - }, - { - name: "Get all Chronograf users, ensuring order of users in response", - fields: fields{ - Logger: &chronograf.NoopLogger{}, - UsersStore: &mocks.UsersStore{ - AllF: func(ctx context.Context) ([]chronograf.User, error) { - return []chronograf.User{ - { - ID: 1338, - Name: "bobbettastuhvetta", - Provider: "auth0", - Scheme: "oauth2", - }, - { - ID: 1337, - Name: "billysteve", - Provider: "google", - Scheme: "oauth2", - Roles: []chronograf.Role{ - roles.EditorRole, - }, - }, - }, nil - }, - }, - }, - args: args{ - w: httptest.NewRecorder(), - r: httptest.NewRequest( - "GET", - "http://any.url", // can be any valid URL as we are bypassing mux - nil, - ), - }, - wantStatus: http.StatusOK, - wantContentType: "application/json", - wantBody: `{"users":[{"id":"1337","superAdmin":false,"name":"billysteve","provider":"google","scheme":"oauth2","roles":[{"name":"editor"}],"links":{"self":"/chronograf/v1/users/1337"}},{"id":"1338","superAdmin":false,"name":"bobbettastuhvetta","provider":"auth0","scheme":"oauth2","roles":[],"links":{"self":"/chronograf/v1/users/1338"}}],"links":{"self":"/chronograf/v1/users"}}`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Service{ - Store: &mocks.Store{ - UsersStore: tt.fields.UsersStore, - }, - Logger: tt.fields.Logger, - } - - s.Users(tt.args.w, tt.args.r) - - resp := tt.args.w.Result() - content := resp.Header.Get("Content-Type") - body, _ := ioutil.ReadAll(resp.Body) - - if resp.StatusCode != tt.wantStatus { - t.Errorf("%q. Users() = %v, want %v", tt.name, resp.StatusCode, tt.wantStatus) - } - if tt.wantContentType != "" && content != tt.wantContentType { - t.Errorf("%q. Users() = %v, want %v", tt.name, content, tt.wantContentType) - } - if eq, _ := jsonEqual(string(body), tt.wantBody); tt.wantBody != "" && !eq { - t.Errorf("%q. Users() = \n***%v***\n,\nwant\n***%v***", tt.name, string(body), tt.wantBody) - } - }) - } -} - -func TestUserRequest_ValidCreate(t *testing.T) { - type args struct { - u *userRequest - } - tests := []struct { - name string - args args - wantErr bool - err error - }{ - { - name: "Valid", - args: args{ - u: &userRequest{ - ID: 1337, - Name: "billietta", - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, - }, - wantErr: false, - err: nil, - }, - { - name: "Invalid – Name missing", - args: args{ - u: &userRequest{ - ID: 1337, - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, - }, - wantErr: true, - err: fmt.Errorf("name required on Chronograf User request body"), - }, - { - name: "Invalid – Provider missing", - args: args{ - u: &userRequest{ - ID: 1337, - Name: "billietta", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, - }, - wantErr: true, - err: fmt.Errorf("provider required on Chronograf User request body"), - }, - { - name: "Invalid – Scheme missing", - args: args{ - u: &userRequest{ - ID: 1337, - Name: "billietta", - Provider: "auth0", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, - }, - wantErr: true, - err: fmt.Errorf("scheme required on Chronograf User request body"), - }, - { - name: "Invalid roles - bad role name", - args: args{ - u: &userRequest{ - ID: 1337, - Name: "billietta", - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "BilliettaSpecialRole", - Organization: "1", - }, - }, - }, - }, - wantErr: true, - err: fmt.Errorf("unknown role BilliettaSpecialRole. Valid roles are 'member', 'viewer', 'editor', 'admin', and '*'"), - }, - { - name: "Invalid roles - missing organization", - args: args{ - u: &userRequest{ - ID: 1337, - Name: "billietta", - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - }, - }, - }, - }, - wantErr: true, - err: fmt.Errorf("no organization was provided"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.args.u.ValidCreate() - - if tt.wantErr { - if err == nil || err.Error() != tt.err.Error() { - t.Errorf("%q. ValidCreate(): wantErr %v,\nwant %v,\ngot %v", tt.name, tt.wantErr, tt.err, err) - } - } else { - if err != nil { - t.Errorf("%q. ValidCreate(): wantErr %v,\nwant %v,\ngot %v", tt.name, tt.wantErr, tt.err, err) - } - } - }) - } -} - -func TestUserRequest_ValidUpdate(t *testing.T) { - type args struct { - u *userRequest - } - tests := []struct { - name string - args args - wantErr bool - err error - }{ - { - name: "Valid", - args: args{ - u: &userRequest{ - ID: 1337, - Roles: []chronograf.Role{ - { - Name: roles.EditorRoleName, - Organization: "1", - }, - }, - }, - }, - wantErr: false, - err: nil, - }, - { - name: "Invalid – roles missing", - args: args{ - u: &userRequest{}, - }, - wantErr: true, - err: fmt.Errorf("no Roles to update"), - }, - { - name: "Invalid - bad role name", - args: args{ - u: &userRequest{ - ID: 1337, - Name: "billietta", - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "BillietaSpecialOrg", - Organization: "0", - }, - }, - }, - }, - wantErr: true, - err: fmt.Errorf("unknown role BillietaSpecialOrg. Valid roles are 'member', 'viewer', 'editor', 'admin', and '*'"), - }, - { - name: "Valid – roles empty", - args: args{ - u: &userRequest{ - ID: 1337, - Name: "billietta", - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{}, - }, - }, - wantErr: false, - }, - { - name: "Invalid - bad role name", - args: args{ - u: &userRequest{ - ID: 1337, - Name: "billietta", - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: "BillietaSpecialOrg", - Organization: "0", - }, - }, - }, - }, - wantErr: true, - err: fmt.Errorf("unknown role BillietaSpecialOrg. Valid roles are 'member', 'viewer', 'editor', 'admin', and '*'"), - }, - { - name: "Invalid - duplicate organization", - args: args{ - u: &userRequest{ - ID: 1337, - Name: "billietta", - Provider: "auth0", - Scheme: "oauth2", - Roles: []chronograf.Role{ - { - Name: roles.AdminRoleName, - Organization: "0", - }, - { - Name: roles.ViewerRoleName, - Organization: "0", - }, - }, - }, - }, - wantErr: true, - err: fmt.Errorf("duplicate organization \"0\" in roles"), - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.args.u.ValidUpdate() - - if tt.wantErr { - if err == nil || err.Error() != tt.err.Error() { - t.Errorf("%q. ValidUpdate(): wantErr %v,\nwant %v,\ngot %v", tt.name, tt.wantErr, tt.err, err) - } - } else { - if err != nil { - t.Errorf("%q. ValidUpdate(): wantErr %v,\nwant %v,\ngot %v", tt.name, tt.wantErr, tt.err, err) - } - } - }) - } -} diff --git a/chronograf/server/version.go b/chronograf/server/version.go deleted file mode 100644 index e7fc4c9013d..00000000000 --- a/chronograf/server/version.go +++ /dev/null @@ -1,14 +0,0 @@ -package server - -import ( - "net/http" -) - -// Version handler adds X-Chronograf-Version header to responses -func Version(version string, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("X-Chronograf-Version", version) - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} diff --git a/cmd/chronograf-migrator/README.md b/cmd/chronograf-migrator/README.md deleted file mode 100644 index a8fd9214962..00000000000 --- a/cmd/chronograf-migrator/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Chronograf Migrator - -This tool is used to migrate `1.x` Chronograf `Dashboards` and `Template Variables` to their `2.x` -equivalents using `pkger` packages. The tool expects the user to have the 1.x Chronograf database. - -```sh -chronograf-migrator -h -Usage of chronograf-migrator: - -db string - path to the chronograf database - -output string - path to the output yaml file (default "dashboards.yml") -``` - -## Example Usage - -```sh -$ chronograf-migrator -db chronograf-v1.db -output dashboards.yml -$ INFLUX_TOKEN= influx pkg -o -f dashboards.yml -``` diff --git a/cmd/chronograf-migrator/dashboard.go b/cmd/chronograf-migrator/dashboard.go deleted file mode 100644 index cb4f693da41..00000000000 --- a/cmd/chronograf-migrator/dashboard.go +++ /dev/null @@ -1,404 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" - "regexp" - "strings" - "time" - - "github.com/influxdata/flux/ast" - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/query/influxql" -) - -func convert1To2Cell(cell chronograf.DashboardCell) *influxdb.Cell { - c := &influxdb.Cell{ - ID: 1, - CellProperty: influxdb.CellProperty{ - X: cell.X, - Y: cell.Y, - W: cell.W, - H: cell.H, - }, - } - - v := influxdb.View{ - ViewContents: influxdb.ViewContents{ - Name: cell.Name, - }, - } - - switch cell.Type { - case "line": - v.Properties = influxdb.XYViewProperties{ - Queries: convertQueries(cell.Queries), - Axes: convertAxes(cell.Axes), - Type: "xy", - Legend: convertLegend(cell.Legend), - Geom: "line", - ViewColors: convertColors(cell.CellColors), - Note: cell.Note, - Position: "overlaid", - } - case "line-stacked": - v.Properties = influxdb.XYViewProperties{ - Queries: convertQueries(cell.Queries), - Axes: convertAxes(cell.Axes), - Type: "xy", - Legend: convertLegend(cell.Legend), - Geom: "line", // TODO(desa): maybe this needs to be stacked? - ViewColors: convertColors(cell.CellColors), - Note: cell.Note, - Position: "stacked", - } - case "line-stepplot": - v.Properties = influxdb.XYViewProperties{ - Queries: convertQueries(cell.Queries), - Axes: convertAxes(cell.Axes), - Type: "xy", - Legend: convertLegend(cell.Legend), - Geom: "step", - ViewColors: convertColors(cell.CellColors), - Note: cell.Note, - Position: "overlaid", - } - case "bar": - v.Properties = influxdb.XYViewProperties{ - Queries: convertQueries(cell.Queries), - Axes: convertAxes(cell.Axes), - Type: "xy", - Legend: convertLegend(cell.Legend), - Geom: "bar", - ViewColors: convertColors(cell.CellColors), - Note: cell.Note, - Position: "overlaid", - } - case "line-plus-single-stat": - v.Properties = influxdb.LinePlusSingleStatProperties{ - Queries: convertQueries(cell.Queries), - Axes: convertAxes(cell.Axes), - Legend: convertLegend(cell.Legend), - ViewColors: convertColors(cell.CellColors), - Note: cell.Note, - Position: "overlaid", - } - case "single-stat": - v.Properties = influxdb.SingleStatViewProperties{ - Queries: convertQueries(cell.Queries), - ViewColors: convertColors(cell.CellColors), - Note: cell.Note, - // TODO(desa): what to do about ShowNoteWhenEmpty? - } - case "gauge": - v.Properties = influxdb.GaugeViewProperties{ - Queries: convertQueries(cell.Queries), - ViewColors: convertColors(cell.CellColors), - Note: cell.Note, - // TODO(desa): what to do about ShowNoteWhenEmpty? - } - case "table": - v.Properties = influxdb.TableViewProperties{ - Queries: convertQueries(cell.Queries), - ViewColors: convertColors(cell.CellColors), - //TableOptions - //FieldOptions - Note: cell.Note, - // TODO(desa): what to do about ShowNoteWhenEmpty? - } - case "note": - v.Properties = influxdb.MarkdownViewProperties{ - Note: cell.Note, - } - case "alerts", "news", "guide": - // TODO(desa): these do not have 2.x equivalents - v.Properties = influxdb.EmptyViewProperties{} - default: - v.Properties = influxdb.EmptyViewProperties{} - } - - c.View = &v - - return c -} - -func convert1To2Variable(t chronograf.Template) (influxdb.Variable, error) { - v := influxdb.Variable{ - Description: t.Label, - Name: t.Var[1 : len(t.Var)-1], // trims `:` from variables prefix and suffix - } - - switch t.Type { - case "influxql", "databases", "fieldKeys", "tagKeys", "tagValues", "measurements": - if t.Query == nil { - return v, fmt.Errorf("expected template variable to have non-nil query") - } - } - - switch t.Type { - case "influxql": - v.Arguments = &influxdb.VariableArguments{ - Type: "query", - Values: influxdb.VariableQueryValues{ - Query: fmt.Sprintf("// %s", t.Query.Command), - Language: "flux", - }, - } - case "databases": - v.Arguments = &influxdb.VariableArguments{ - Type: "query", - Values: influxdb.VariableQueryValues{ - Query: fmt.Sprintf("// SHOW DATABASES %s", t.Query.DB), - Language: "flux", - }, - } - case "fieldKeys": - v.Arguments = &influxdb.VariableArguments{ - Type: "query", - Values: influxdb.VariableQueryValues{ - Query: fmt.Sprintf("// SHOW FIELD KEYS FOR %s", t.Query.Measurement), - Language: "flux", - }, - } - case "tagKeys": - v.Arguments = &influxdb.VariableArguments{ - Type: "query", - Values: influxdb.VariableQueryValues{ - Query: fmt.Sprintf("// SHOW TAG KEYS FOR %s", t.Query.Measurement), - Language: "flux", - }, - } - case "tagValues": - v.Arguments = &influxdb.VariableArguments{ - Type: "query", - Values: influxdb.VariableQueryValues{ - Query: fmt.Sprintf("// SHOW TAG VALUES FOR %s", t.Query.TagKey), - Language: "flux", - }, - } - case "measurements": - v.Arguments = &influxdb.VariableArguments{ - Type: "query", - Values: influxdb.VariableQueryValues{ - Query: fmt.Sprintf("// SHOW MEASUREMENTS ON %s", t.Query.DB), - Language: "flux", - }, - } - case "csv", "constant", "text": - values := influxdb.VariableConstantValues{} - for _, val := range t.Values { - values = append(values, val.Value) - } - v.Arguments = &influxdb.VariableArguments{ - Type: "constant", - Values: values, - } - case "map": - values := influxdb.VariableMapValues{} - for _, val := range t.Values { - values[val.Key] = val.Value - } - v.Arguments = &influxdb.VariableArguments{ - Type: "map", - Values: values, - } - default: - return v, fmt.Errorf("unknown variable type %s", t.Type) - } - - return v, nil -} - -func Convert1To2Dashboard(d1 chronograf.Dashboard) (influxdb.Dashboard, []influxdb.Variable, error) { - cells := []*influxdb.Cell{} - for _, cell := range d1.Cells { - cells = append(cells, convert1To2Cell(cell)) - } - - d2 := influxdb.Dashboard{ - Name: d1.Name, - Cells: cells, - } - - vars := []influxdb.Variable{} - for _, template := range d1.Templates { - v, err := convert1To2Variable(template) - if err != nil { - return influxdb.Dashboard{}, nil, err - } - - vars = append(vars, v) - } - - return d2, vars, nil -} - -func convertAxes(a map[string]chronograf.Axis) map[string]influxdb.Axis { - m := map[string]influxdb.Axis{} - for k, v := range a { - m[k] = influxdb.Axis{ - Bounds: v.Bounds, - Label: v.Label, - Prefix: v.Prefix, - Suffix: v.Suffix, - Base: v.Base, - Scale: v.Scale, - } - } - - if _, exists := m["x"]; !exists { - m["x"] = influxdb.Axis{} - } - if _, exists := m["y"]; !exists { - m["y"] = influxdb.Axis{} - } - - return m -} - -func convertLegend(l chronograf.Legend) influxdb.Legend { - return influxdb.Legend{ - Type: l.Type, - Orientation: l.Orientation, - } -} - -func convertColors(cs []chronograf.CellColor) []influxdb.ViewColor { - vs := []influxdb.ViewColor{} - - hasTextColor := false - hasThresholdColor := false - for _, c := range cs { - if c.Type == "text" { - hasTextColor = true - } - if c.Type == "threshold" { - hasThresholdColor = true - } - - v := influxdb.ViewColor{ - ID: c.ID, - Type: c.Type, - Hex: c.Hex, - Name: c.Name, - } - vs = append(vs, v) - } - - if !hasTextColor { - vs = append(vs, influxdb.ViewColor{ - ID: "base", - Type: "text", - Hex: "#00C9FF", - Name: "laser", - Value: 0, - }) - } - - if !hasThresholdColor { - vs = append(vs, influxdb.ViewColor{ - ID: "t", - Type: "threshold", - Hex: "#4591ED", - Name: "ocean", - Value: 80, - }) - } - - return vs -} - -var influxQLVarPattern = regexp.MustCompile(`'?:(\w+):'?`) - -func transpileQuery(q string) (string, error) { - now := time.Now() - t := influxql.NewTranspilerWithConfig(dbrpMapper{}, influxql.Config{ - Now: now, - FallbackToDBRP: true, - }) - - query := q - query = strings.Replace(query, ":interval:", "8675309ns", -1) - query = strings.Replace(query, ":dashboardTime:", "now() - 15m", 1) - query = strings.Replace(query, ":upperDashboardTime:", "now()", 1) - - // TODO(desa): replace all variables not using this hack - query = influxQLVarPattern.ReplaceAllString(query, "'$1'") - - pkg, err := t.Transpile(context.Background(), query) - if err != nil { - return "", err - } - - return ast.Format(pkg), nil -} - -func convertQueries(qs []chronograf.DashboardQuery) []influxdb.DashboardQuery { - - ds := []influxdb.DashboardQuery{} - for _, q := range qs { - queryText := q.Command - if q.Type == "influxql" { - // if the query is influxql, add it as a comment and attempt to - // compile it to flux - queryText = fmt.Sprintf("// %s", queryText) - - tq, err := transpileQuery(q.Command) - if err != nil { - queryText = fmt.Sprintf("// Failed to transpile query: %v\n%s", err, queryText) - } else { - queryText = fmt.Sprintf("// Original Query:\n%s\n\n%s", queryText, tq) - } - } - - d := influxdb.DashboardQuery{ - Text: queryText, - EditMode: "advanced", - } - - ds = append(ds, d) - } - - if len(ds) == 0 { - d := influxdb.DashboardQuery{ - Text: "// cell had no queries", - EditMode: "advanced", - BuilderConfig: influxdb.BuilderConfig{ - // TODO(desa): foo - Buckets: []string{"bucket"}, - }, - } - ds = append(ds, d) - } - - return ds -} - -type dbrpMapper struct{} - -// FindBy returns the dbrp mapping for the specified ID. -func (d dbrpMapper) FindByID(ctx context.Context, orgID influxdb.ID, id influxdb.ID) (*influxdb.DBRPMappingV2, error) { - return nil, errors.New("mapping not found") -} - -// FindMany returns a list of dbrp mappings that match filter and the total count of matching dbrp mappings. -func (d dbrpMapper) FindMany(ctx context.Context, dbrp influxdb.DBRPMappingFilterV2, opts ...influxdb.FindOptions) ([]*influxdb.DBRPMappingV2, int, error) { - return nil, 0, errors.New("mapping not found") -} - -// Create creates a new dbrp mapping, if a different mapping exists an error is returned. -func (d dbrpMapper) Create(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error { - return errors.New("dbrpMapper does not support creating new mappings") -} - -// Update a new dbrp mapping -func (d dbrpMapper) Update(ctx context.Context, dbrp *influxdb.DBRPMappingV2) error { - return errors.New("dbrpMapper does not support updating mappings") -} - -// Delete removes a dbrp mapping. -func (d dbrpMapper) Delete(ctx context.Context, orgID influxdb.ID, id influxdb.ID) error { - return errors.New("dbrpMapper does not support deleting mappings") -} diff --git a/cmd/chronograf-migrator/main.go b/cmd/chronograf-migrator/main.go deleted file mode 100644 index 2bb44d3e822..00000000000 --- a/cmd/chronograf-migrator/main.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "bytes" - "context" - "flag" - "fmt" - "io" - "log" - "os" - "strings" - - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/bolt" - "github.com/influxdata/influxdb/v2/pkger" -) - -var chronografDBPath string -var outputFile string - -func exec(dbPath, out string) error { - logger := log.New(os.Stdout, "", 0) - - c := bolt.NewClient() - c.Path = dbPath - - ctx := context.Background() - - if err := c.Open(ctx, nil, chronograf.BuildInfo{}); err != nil { - return err - } - - dashboardStore := c.DashboardsStore - - ds, err := dashboardStore.All(ctx) - if err != nil { - return err - } - - pkg := &pkger.Template{ - Objects: make([]pkger.Object, 0), - } - - hasVar := map[string]bool{} - for _, d1 := range ds { - d2, vs, err := Convert1To2Dashboard(d1) - if err != nil { - return err - } - - pkg.Objects = append(pkg.Objects, pkger.DashboardToObject(d2.Name, d2)) - - for _, v := range vs { - name := strings.ToLower(v.Name) - if hasVar[name] { - // TODO(desa): not sure what we actually want to do here - logger.Printf("Found duplicate variables with name %q skipping\n", name) - continue - } - hasVar[name] = true - - pkg.Objects = append(pkg.Objects, pkger.VariableToObject(name, v)) - } - } - - f, err := os.Create(out) - if err != nil { - return err - } - defer f.Close() - - b, err := pkg.Encode(pkger.EncodingYAML) - if err != nil { - return err - } - _, err = io.Copy(f, bytes.NewReader(b)) - return err -} - -func main() { - flag.StringVar(&chronografDBPath, "db", "", "path to the chronograf database") - flag.StringVar(&outputFile, "output", "dashboards.yml", "path to the output yaml file") - flag.Parse() - - if chronografDBPath == "" { - fmt.Fprintln(os.Stdout, "must supply db flag") - return - } - - if err := exec(chronografDBPath, outputFile); err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - } -} diff --git a/cmd/influxd/launcher/launcher.go b/cmd/influxd/launcher/launcher.go index 32bf875db9a..b9c4abe6844 100644 --- a/cmd/influxd/launcher/launcher.go +++ b/cmd/influxd/launcher/launcher.go @@ -21,7 +21,6 @@ import ( "github.com/influxdata/influxdb/v2/authorizer" "github.com/influxdata/influxdb/v2/bolt" "github.com/influxdata/influxdb/v2/checks" - "github.com/influxdata/influxdb/v2/chronograf/server" "github.com/influxdata/influxdb/v2/dashboards" dashboardTransport "github.com/influxdata/influxdb/v2/dashboards/transport" "github.com/influxdata/influxdb/v2/dbrp" @@ -362,12 +361,6 @@ func (m *Launcher) run(ctx context.Context, opts *InfluxdOpts) (err error) { return err } - chronografSvc, err := server.NewServiceV2(ctx, m.boltClient.DB()) - if err != nil { - m.log.Error("Failed creating chronograf service", zap.Error(err)) - return err - } - metaClient := meta.NewClient(meta.NewConfig(), m.kvStore) if err := metaClient.Open(); err != nil { m.log.Error("Failed to open meta client", zap.Error(err)) @@ -761,7 +754,6 @@ func (m *Launcher) run(ctx context.Context, opts *InfluxdOpts) (err error) { NotificationEndpointService: notificationEndpointSvc, CheckService: checkSvc, ScraperTargetStoreService: scraperTargetSvc, - ChronografService: chronografSvc, SecretService: secretSvc, LookupService: resourceResolver, DocumentService: m.kvService, diff --git a/cmd/influxd/launcher/launcher_test.go b/cmd/influxd/launcher/launcher_test.go index fa146a4a613..f8250012892 100644 --- a/cmd/influxd/launcher/launcher_test.go +++ b/cmd/influxd/launcher/launcher_test.go @@ -46,7 +46,7 @@ func TestLauncher_Setup(t *testing.T) { } } -// This is to mimic chronograf using cookies as sessions +// This is to mimic the UI using cookies as sessions // rather than authorizations func TestLauncher_SetupWithUsers(t *testing.T) { l := launcher.RunAndSetupNewLauncherOrFail(ctx, t) diff --git a/error.go b/error.go deleted file mode 100644 index 42c34a1d14a..00000000000 --- a/error.go +++ /dev/null @@ -1,9 +0,0 @@ -package influxdb - -// ChronografError is a domain error encountered while processing chronograf requests. -type ChronografError string - -// ChronografError returns the string of an error. -func (e ChronografError) Error() string { - return string(e) -} diff --git a/flux/client.go b/flux/client.go index d374ec484c5..94f0636fa48 100644 --- a/flux/client.go +++ b/flux/client.go @@ -8,8 +8,6 @@ import ( "net/http" "net/url" "time" - - "github.com/influxdata/influxdb/v2/chronograf" ) // Shared transports for all clients to prevent leaking connections. @@ -49,7 +47,7 @@ func (c *Client) pingTimeout(ctx context.Context) error { case resp := <-resps: return resp case <-ctx.Done(): - return chronograf.ErrUpstreamTimeout + return fmt.Errorf("request to backend timed out") } } diff --git a/go.mod b/go.mod index a04e968bddc..d1c15436a45 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3 github.com/benbjohnson/tmpl v1.0.0 github.com/boltdb/bolt v1.3.1 // indirect - github.com/bouk/httprouter v0.0.0-20160817010721-ee8b3818a7f5 github.com/buger/jsonparser v0.0.0-20191004114745-ee4c978eae7e github.com/cespare/xxhash v1.1.0 github.com/davecgh/go-spew v1.1.1 @@ -34,9 +33,7 @@ require ( github.com/golang/snappy v0.0.1 github.com/google/btree v1.0.0 github.com/google/go-cmp v0.5.5 - github.com/google/go-github v17.0.0+incompatible github.com/google/go-jsonnet v0.17.0 - github.com/google/go-querystring v1.0.0 // indirect github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible // indirect github.com/hashicorp/go-msgpack v0.0.0-20150518234257-fa3f63826f7c // indirect github.com/hashicorp/go-retryablehttp v0.6.4 // indirect @@ -49,8 +46,6 @@ require ( github.com/influxdata/influx-cli/v2 v2.0.0-20210716211743-ef4bdcb9b1e4 github.com/influxdata/influxql v0.0.0-20180925231337-1cbfca8e56b6 github.com/influxdata/pkg-config v0.2.7 - github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368 - github.com/jessevdk/go-flags v1.4.0 github.com/jsternberg/zap-logfmt v1.2.0 github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef github.com/kevinburke/go-bindata v3.11.0+incompatible @@ -81,7 +76,6 @@ require ( github.com/stretchr/testify v1.7.0 github.com/testcontainers/testcontainers-go v0.0.0-20190108154635-47c0da630f72 github.com/tinylib/msgp v1.1.0 - github.com/tylerb/graceful v1.2.15 github.com/uber/jaeger-client-go v2.28.0+incompatible github.com/willf/bitset v1.1.9 // indirect github.com/xlab/treeprint v1.0.0 @@ -92,14 +86,11 @@ require ( go.uber.org/multierr v1.5.0 go.uber.org/zap v1.14.1 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad - golang.org/x/net v0.0.0-20210119194325-5f4716e94777 - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c golang.org/x/text v0.3.5 golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba golang.org/x/tools v0.1.0 - google.golang.org/api v0.17.0 gopkg.in/vmihailenco/msgpack.v2 v2.9.1 // indirect gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c diff --git a/go.sum b/go.sum index 373c3d344cb..114369aa36f 100644 --- a/go.sum +++ b/go.sum @@ -109,8 +109,6 @@ github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bonitoo-io/go-sql-bigquery v0.3.4-1.4.0 h1:MaVh0h9+KaMnJcoDvvIGp+O3fefdWm+8MBUX6ELTJTM= github.com/bonitoo-io/go-sql-bigquery v0.3.4-1.4.0/go.mod h1:J4Y6YJm0qTWB9aFziB7cPeSyc6dOZFyJdteSeybVpXQ= -github.com/bouk/httprouter v0.0.0-20160817010721-ee8b3818a7f5 h1:kS0dw4K730x7cxT+bVyTyYJZHuSoH7ofSr/Ijit56Qw= -github.com/bouk/httprouter v0.0.0-20160817010721-ee8b3818a7f5/go.mod h1:CDReaxg1cmLrtcasZy43l4EYPAknXLiQSrb7tLw5zXM= github.com/buger/jsonparser v0.0.0-20191004114745-ee4c978eae7e h1:oJCXMss/3rg5F6Poy9wG3JQusc58Mzk5B9Z6wSnssNE= github.com/buger/jsonparser v0.0.0-20191004114745-ee4c978eae7e/go.mod h1:errmMKH8tTB49UR2A8C8DPYkyudelsYJwJFaZHQ6ik8= github.com/c-bata/go-prompt v0.2.2 h1:uyKRz6Z6DUyj49QVijyM339UJV9yhbr70gESwbNU3e0= @@ -256,12 +254,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-jsonnet v0.17.0 h1:/9NIEfhK1NQRKl3sP2536b2+x5HnZMdql7x3yK/l8JY= github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE= @@ -359,10 +353,6 @@ github.com/influxdata/pkg-config v0.2.7/go.mod h1:EMS7Ll0S4qkzDk53XS3Z72/egBsPIn github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b h1:i44CesU68ZBRvtCjBi3QSosCIKrjmMbYlQMFAwVLds4= github.com/influxdata/tdigest v0.0.2-0.20210216194612-fc98d27c9e8b/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368 h1:+TUUmaFa4YD1Q+7bH9o5NCHQGPMqZCYJiNW6lIIS9z4= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -582,8 +572,6 @@ github.com/testcontainers/testcontainers-go v0.0.0-20190108154635-47c0da630f72/g github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tylerb/graceful v1.2.15 h1:B0x01Y8fsJpogzZTkDg6BDi6eMf03s01lEKGdrv83oA= -github.com/tylerb/graceful v1.2.15/go.mod h1:LPYTbOYmUTdabwRt0TGhLllQ0MUNbs0Y5q1WXJOI9II= github.com/uber-go/tally v3.3.15+incompatible h1:9hLSgNBP28CjIaDmAuRTq9qV+UZY+9PcvAkXO4nNMwg= github.com/uber-go/tally v3.3.15+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= github.com/uber/athenadriver v1.1.4 h1:k6k0RBeXjR7oZ8NO557MsRw3eX1cc/9B0GNx+W9eHiQ= diff --git a/http/Makefile b/http/Makefile deleted file mode 100644 index 29c96037d71..00000000000 --- a/http/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# List any directories that have their own Makefile here -SUBDIRS = - -# Default target -all: $(SUBDIRS) swagger_gen.go - -# Recurse into subdirs for same make goal -$(SUBDIRS): - $(MAKE) -C $@ $(MAKECMDGOALS) - -# Clean all targets recursively -clean: $(SUBDIRS) - rm -f swagger_gen.go - -swagger_gen.go: swagger.go redoc.go swagger.yml - go generate -x - @echo '//lint:file-ignore ST1005 Ignore error strings should not be capitalized' >> swagger_gen.go - -GO_RUN := env GO111MODULE=on go run - -.PHONY: all clean $(SUBDIRS) diff --git a/http/api_handler.go b/http/api_handler.go index e3f5e7baa39..6e77e765486 100644 --- a/http/api_handler.go +++ b/http/api_handler.go @@ -8,7 +8,6 @@ import ( "github.com/influxdata/httprouter" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/authorizer" - "github.com/influxdata/influxdb/v2/chronograf/server" "github.com/influxdata/influxdb/v2/dbrp" "github.com/influxdata/influxdb/v2/http/metric" "github.com/influxdata/influxdb/v2/influxql" @@ -16,6 +15,7 @@ import ( "github.com/influxdata/influxdb/v2/kit/prom" kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" "github.com/influxdata/influxdb/v2/query" + "github.com/influxdata/influxdb/v2/static" "github.com/influxdata/influxdb/v2/storage" "github.com/prometheus/client_golang/prometheus" "go.uber.org/zap" @@ -91,7 +91,6 @@ type APIBackend struct { ScraperTargetStoreService influxdb.ScraperTargetStoreService SecretService influxdb.SecretService LookupService influxdb.LookupService - ChronografService *server.Service OrgLookupService authorizer.OrgIDResolver DocumentService influxdb.DocumentService NotificationRuleStore influxdb.NotificationRuleStore @@ -141,8 +140,6 @@ func NewAPIHandler(b *APIBackend, opts ...APIHandlerOptFn) *APIHandler { b.UserResourceMappingService, b.OrganizationService) h.Mount(prefixChecks, NewCheckHandler(b.Logger, checkBackend)) - h.Mount(prefixChronograf, NewChronografHandler(b.ChronografService, b.HTTPErrorHandler)) - deleteBackend := NewDeleteBackend(b.Logger.With(zap.String("handler", "delete")), b) h.Mount(prefixDelete, NewDeleteHandler(b.Logger, deleteBackend)) @@ -174,7 +171,7 @@ func NewAPIHandler(b *APIBackend, opts ...APIHandlerOptFn) *APIHandler { sourceBackend.BucketService = authorizer.NewBucketService(b.BucketService) h.Mount(prefixSources, NewSourceHandler(b.Logger, sourceBackend)) - h.Mount("/api/v2/swagger.json", newSwaggerLoader(b.Logger.With(zap.String("service", "swagger-loader")), b.HTTPErrorHandler)) + h.Mount("/api/v2/swagger.json", static.NewSwaggerHandler(b.Logger.With(zap.String("service", "swagger-loader")), b.HTTPErrorHandler)) taskLogger := b.Logger.With(zap.String("handler", "bucket")) taskBackend := NewTaskBackend(taskLogger, b) diff --git a/http/assets.go b/http/assets.go deleted file mode 100644 index 60a20c9a680..00000000000 --- a/http/assets.go +++ /dev/null @@ -1,50 +0,0 @@ -package http - -import ( - "net/http" - "path/filepath" - - // TODO: use platform version of the code - "github.com/influxdata/influxdb/v2/chronograf" - "github.com/influxdata/influxdb/v2/chronograf/dist" -) - -const ( - // Dir is prefix of the assets in the bindata - Dir = "../../ui/build" - // Default is the default item to load if 404 - Default = "../../ui/build/index.html" - // DebugDefault is the default item to load if 404 - DebugDefault = "index.html" - // DefaultContentType is the content-type to return for the Default file - DefaultContentType = "text/html; charset=utf-8" -) - -// AssetHandler is an http handler for serving chronograf assets. -type AssetHandler struct { - Path string -} - -// NewAssetHandler is the constructor an asset handler. -func NewAssetHandler() *AssetHandler { - return &AssetHandler{} -} - -// ServeHTTP implements the http handler interface for serving assets. -func (h *AssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - var assets chronograf.Assets - if h.Path != "" { - assets = &dist.DebugAssets{ - Dir: h.Path, - Default: filepath.Join(h.Path, DebugDefault), - } - } else { - assets = &dist.BindataAssets{ - Prefix: Dir, - Default: Default, - DefaultContentType: DefaultContentType, - } - } - - assets.Handler().ServeHTTP(w, r) -} diff --git a/http/chronograf_handler.go b/http/chronograf_handler.go deleted file mode 100644 index e66a676d8d4..00000000000 --- a/http/chronograf_handler.go +++ /dev/null @@ -1,110 +0,0 @@ -package http - -import ( - "github.com/influxdata/httprouter" - "github.com/influxdata/influxdb/v2" - "github.com/influxdata/influxdb/v2/chronograf/server" -) - -const prefixChronograf = "/chronograf" - -// ChronografHandler is an http handler for serving chronograf chronografs. -type ChronografHandler struct { - *httprouter.Router - Service *server.Service -} - -// NewChronografHandler is the constructor an chronograf handler. -func NewChronografHandler(s *server.Service, he influxdb.HTTPErrorHandler) *ChronografHandler { - h := &ChronografHandler{ - Router: NewRouter(he), - Service: s, - } - /* API */ - // Organizations - h.HandlerFunc("GET", "/chronograf/v1/organizations", h.Service.Organizations) - h.HandlerFunc("POST", "/chronograf/v1/organizations", h.Service.NewOrganization) - - h.HandlerFunc("GET", "/chronograf/v1/organizations/:oid", h.Service.OrganizationID) - h.HandlerFunc("PATCH", "/chronograf/v1/organizations/:oid", h.Service.UpdateOrganization) - h.HandlerFunc("DELETE", "/chronograf/v1/organizations/:oid", h.Service.RemoveOrganization) - - // Mappings - h.HandlerFunc("GET", "/chronograf/v1/mappings", h.Service.Mappings) - h.HandlerFunc("POST", "/chronograf/v1/mappings", h.Service.NewMapping) - - h.HandlerFunc("PUT", "/chronograf/v1/mappings/:id", h.Service.UpdateMapping) - h.HandlerFunc("DELETE", "/chronograf/v1/mappings/:id", h.Service.RemoveMapping) - - // Layouts - h.HandlerFunc("GET", "/chronograf/v1/layouts", h.Service.Layouts) - h.HandlerFunc("GET", "/chronograf/v1/layouts/:id", h.Service.LayoutsID) - - // Users associated with Chronograf - h.HandlerFunc("GET", "/chronograf/v1/me", h.Service.Me) - - // TODO(desa): what to do here? - // Set current chronograf organization the user is logged into - //h.HandlerFunc("PUT", "/chronograf/v1/me", h.Service.UpdateMe(opts.Auth)) - - // TODO(desa): what to do about admin's being able to set superadmin - h.HandlerFunc("GET", "/chronograf/v1/organizations/:oid/users", h.Service.Users) - h.HandlerFunc("POST", "/chronograf/v1/organizations/:oid/users", h.Service.NewUser) - - h.HandlerFunc("GET", "/chronograf/v1/organizations/:oid/users/:id", h.Service.UserID) - h.HandlerFunc("DELETE", "/chronograf/v1/organizations/:oid/users/:id", h.Service.RemoveUser) - h.HandlerFunc("PATCH", "/chronograf/v1/organizations/:oid/users/:id", h.Service.UpdateUser) - - h.HandlerFunc("GET", "/chronograf/v1/users", h.Service.Users) - h.HandlerFunc("POST", "/chronograf/v1/users", h.Service.NewUser) - - h.HandlerFunc("GET", "/chronograf/v1/users/:id", h.Service.UserID) - h.HandlerFunc("DELETE", "/chronograf/v1/users/:id", h.Service.RemoveUser) - h.HandlerFunc("PATCH", "/chronograf/v1/users/:id", h.Service.UpdateUser) - - // Dashboards - h.HandlerFunc("GET", "/chronograf/v1/dashboards", h.Service.Dashboards) - h.HandlerFunc("POST", "/chronograf/v1/dashboards", h.Service.NewDashboard) - - h.HandlerFunc("GET", "/chronograf/v1/dashboards/:id", h.Service.DashboardID) - h.HandlerFunc("DELETE", "/chronograf/v1/dashboards/:id", h.Service.RemoveDashboard) - h.HandlerFunc("PUT", "/chronograf/v1/dashboards/:id", h.Service.ReplaceDashboard) - h.HandlerFunc("PATCH", "/chronograf/v1/dashboards/:id", h.Service.UpdateDashboard) - // Dashboard Cells - h.HandlerFunc("GET", "/chronograf/v1/dashboards/:id/cells", h.Service.DashboardCells) - h.HandlerFunc("POST", "/chronograf/v1/dashboards/:id/cells", h.Service.NewDashboardCell) - - h.HandlerFunc("GET", "/chronograf/v1/dashboards/:id/cells/:cid", h.Service.DashboardCellID) - h.HandlerFunc("DELETE", "/chronograf/v1/dashboards/:id/cells/:cid", h.Service.RemoveDashboardCell) - h.HandlerFunc("PUT", "/chronograf/v1/dashboards/:id/cells/:cid", h.Service.ReplaceDashboardCell) - // Dashboard Templates - h.HandlerFunc("GET", "/chronograf/v1/dashboards/:id/templates", h.Service.Templates) - h.HandlerFunc("POST", "/chronograf/v1/dashboards/:id/templates", h.Service.NewTemplate) - - h.HandlerFunc("GET", "/chronograf/v1/dashboards/:id/templates/:tid", h.Service.TemplateID) - h.HandlerFunc("DELETE", "/chronograf/v1/dashboards/:id/templates/:tid", h.Service.RemoveTemplate) - h.HandlerFunc("PUT", "/chronograf/v1/dashboards/:id/templates/:tid", h.Service.ReplaceTemplate) - - // Global application config for Chronograf - h.HandlerFunc("GET", "/chronograf/v1/config", h.Service.Config) - h.HandlerFunc("GET", "/chronograf/v1/config/auth", h.Service.AuthConfig) - h.HandlerFunc("PUT", "/chronograf/v1/config/auth", h.Service.ReplaceAuthConfig) - - // Organization config settings for Chronograf - h.HandlerFunc("GET", "/chronograf/v1/org_config", h.Service.OrganizationConfig) - h.HandlerFunc("GET", "/chronograf/v1/org_config/logviewer", h.Service.OrganizationLogViewerConfig) - h.HandlerFunc("PUT", "/chronograf/v1/org_config/logviewer", h.Service.ReplaceOrganizationLogViewerConfig) - - h.HandlerFunc("GET", "/chronograf/v1/env", h.Service.Environment) - - allRoutes := &server.AllRoutes{ - // TODO(desa): what to do here - //logger: opts.logger, - //CustomLinks: opts.CustomLinks, - StatusFeed: "https://www.influxdata.com/feed/json", - } - - h.Handler("GET", "/chronograf/v1/", allRoutes) - - return h -} diff --git a/http/no_assets.go b/http/no_assets.go deleted file mode 100644 index ee895a248c3..00000000000 --- a/http/no_assets.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build !assets - -package http - -import ( - "errors" -) - -// The functions defined in this file are placeholders when the binary is compiled -// without assets. - -// Asset returns an error stating no assets were included in the binary. -func Asset(string) ([]byte, error) { - return nil, errors.New("no assets included in binary") -} diff --git a/http/platform_handler.go b/http/platform_handler.go index 31154505742..857428b2686 100644 --- a/http/platform_handler.go +++ b/http/platform_handler.go @@ -7,11 +7,12 @@ import ( "github.com/influxdata/influxdb/v2/http/legacy" "github.com/influxdata/influxdb/v2/kit/feature" kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" + "github.com/influxdata/influxdb/v2/static" ) // PlatformHandler is a collection of all the service handlers. type PlatformHandler struct { - AssetHandler *AssetHandler + AssetHandler http.Handler DocsHandler http.HandlerFunc APIHandler http.Handler LegacyHandler http.Handler @@ -33,8 +34,7 @@ func NewPlatformHandler(b *APIBackend, opts ...APIHandlerOptFn) *PlatformHandler h.RegisterNoAuthRoute("GET", "/api/v2/setup") h.RegisterNoAuthRoute("GET", "/api/v2/swagger.json") - assetHandler := NewAssetHandler() - assetHandler.Path = b.AssetsPath + assetHandler := static.NewAssetHandler(b.AssetsPath) wrappedHandler := kithttp.SetCORS(h) wrappedHandler = kithttp.SkipOptions(wrappedHandler) @@ -65,11 +65,10 @@ func (h *PlatformHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // Serve the chronograf assets for any basepath that does not start with addressable parts - // of the platform API. + // Serve the static UI assets for any basepath that does not start with + // addressable parts of the platform API. if !strings.HasPrefix(r.URL.Path, "/v1") && !strings.HasPrefix(r.URL.Path, "/api/v2") && - !strings.HasPrefix(r.URL.Path, "/chronograf/") && !strings.HasPrefix(r.URL.Path, "/private/") { h.AssetHandler.ServeHTTP(w, r) return diff --git a/http/redoc.go b/http/redoc.go index 9bf1874778c..dc491d4d1ef 100644 --- a/http/redoc.go +++ b/http/redoc.go @@ -8,7 +8,7 @@ import ( const index = ` - Chronograf API + InfluxDB 2 API