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/Makefile b/chronograf/Makefile deleted file mode 100644 index ac1ccb08fec..00000000000 --- a/chronograf/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# List any generated files here -TARGETS = -# List any source files used to generate the targets here -SOURCES = -# List any directories that have their own Makefile here -SUBDIRS = dist server canned - -# 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/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 2234d6533fc..00000000000 --- a/chronograf/bolt/change_interval_to_duration.go +++ /dev/null @@ -1,1392 +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"` - 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) 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 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 296ec417ab9..00000000000 --- a/chronograf/bolt/internal/internal.go +++ /dev/null @@ -1,852 +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, - 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, - } - } - } - - 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, - 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 7d9796fe46c..00000000000 --- a/chronograf/bolt/internal/internal.proto +++ /dev/null @@ -1,241 +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 - 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 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 b1ea34bf27c..00000000000 --- a/chronograf/bolt/internal/internal_test.go +++ /dev/null @@ -1,480 +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", - }, - }, - 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", - }, - }, - 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/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 e316e7f9954..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{ - "": { - "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{ - "": { - "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{ - "": { - "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{ - "": { - "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{ - "": { - "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{ - "": { - "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: []Role{ - { - Name: "timetravelers", - Permissions: map[string][]string{ - "": { - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty", "docbrown"}, - }, - { - Name: "mcfly", - Permissions: map[string][]string{ - "": { - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty", "george"}, - }, - }, - }, - "docbrown": { - Roles: []Role{ - { - Name: "timetravelers", - Permissions: map[string][]string{ - "": { - "ViewAdmin", "ViewChronograf", - }, - }, - Users: []string{"marty", "docbrown"}, - }, - }, - }, - "george": { - Roles: []Role{ - { - Name: "mcfly", - Permissions: map[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/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 5d2419de5de..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{ - { - 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/go.mod b/go.mod index 0a54668cf5d..95383e81d36 100644 --- a/go.mod +++ b/go.mod @@ -32,9 +32,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 @@ -89,13 +87,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/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 efb17c29ad1..38a2679d2a5 100644 --- a/go.sum +++ b/go.sum @@ -253,12 +253,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=