bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/annotate/backend/elastic6.go (about) 1 package backend 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "reflect" 9 "strings" 10 "time" 11 12 "bosun.org/annotate" 13 elastic "github.com/olivere/elastic" 14 ) 15 16 type Elastic6 struct { 17 *elastic.Client 18 index string 19 urls []string 20 simpleClient bool 21 clientOptionFuncs []elastic.ClientOptionFunc 22 maxResults int 23 initialized bool 24 } 25 26 func NewElastic6(urls []string, simpleclient bool, index string, clientoptions []elastic.ClientOptionFunc) *Elastic6 { 27 return &Elastic6{&elastic.Client{}, index, urls, simpleclient, clientoptions, 200, false} 28 } 29 30 func (e *Elastic6) GetAnnotations(start, end *time.Time, fieldFilters ...FieldFilter) (annotate.Annotations, error) { 31 if !e.initialized { 32 return nil, unInitErr 33 } 34 annotations := annotate.Annotations{} 35 filters := []elastic.Query{} 36 if start != nil && end != nil { 37 startQ := elastic.NewRangeQuery(annotate.EndDate).Gte(start) 38 endQ := elastic.NewRangeQuery(annotate.StartDate).Lte(end) 39 filters = append(filters, elastic.NewBoolQuery().Must(startQ, endQ)) 40 } 41 for _, filter := range fieldFilters { 42 switch filter.Field { 43 case annotate.Source, annotate.Host, annotate.CreationUser, annotate.Owner, annotate.Category: 44 default: 45 return annotations, fmt.Errorf("%v is not a field that can be filtered on", filter.Field) 46 } 47 var q elastic.Query 48 switch filter.Verb { 49 case Is, "": 50 q = elastic.NewTermQuery(filter.Field, filter.Value) 51 case Empty: 52 // Can't detect empty on a analyzed field 53 if filter.Field == annotate.Message { 54 return annotations, fmt.Errorf("message field does not support empty searches") 55 } 56 q = elastic.NewTermQuery(filter.Field, "") 57 default: 58 return annotations, fmt.Errorf("%v is not a valid query verb", filter.Verb) 59 } 60 if filter.Not { 61 q = elastic.NewBoolQuery().MustNot(q) 62 } 63 filters = append(filters, q) 64 } 65 66 var aType annotate.Annotation 67 scroll := e.Scroll(e.index).Query(elastic.NewBoolQuery().Must(filters...)).Size(e.maxResults).Pretty(true) 68 for { 69 res, err := scroll.Do(context.Background()) 70 if err == io.EOF { 71 break 72 } 73 if err != nil { 74 return annotations, err 75 } 76 for _, item := range res.Each(reflect.TypeOf(aType)) { 77 a := item.(annotate.Annotation) 78 annotations = append(annotations, a) 79 } 80 } 81 return annotations, nil 82 } 83 84 func (e *Elastic6) GetFieldValues(field string) ([]string, error) { 85 if !e.initialized { 86 return nil, unInitErr 87 } 88 terms := []string{} 89 switch field { 90 case annotate.Source, annotate.Host, annotate.CreationUser, annotate.Owner, annotate.Category: 91 //continue 92 default: 93 return terms, fmt.Errorf("invalid field %v", field) 94 } 95 termsAgg := elastic.NewTermsAggregation().Field(field) 96 res, err := e.Search(e.index).Aggregation(field, termsAgg).Size(e.maxResults).Do(context.Background()) 97 if err != nil { 98 return terms, err 99 } 100 b, found := res.Aggregations.Terms(field) 101 if !found { 102 return terms, fmt.Errorf("expected aggregation %v not found in result", field) 103 } 104 for _, bucket := range b.Buckets { 105 if v, ok := bucket.Key.(string); ok { 106 terms = append(terms, v) 107 } 108 } 109 return terms, nil 110 } 111 112 func (e *Elastic6) InitBackend() error { 113 var err error 114 var ec *elastic.Client 115 116 if e.simpleClient { 117 ec, err = elastic.NewSimpleClient(elastic.SetURL(e.urls...)) 118 } else if len(e.urls) == 0 { 119 ec, err = elastic.NewClient(e.clientOptionFuncs...) 120 } else { 121 ec, err = elastic.NewClient(elastic.SetURL(e.urls...)) 122 } 123 if err != nil { 124 return err 125 } 126 e.Client = ec 127 exists, err := e.IndexExists(e.index).Do(context.Background()) 128 if err != nil { 129 return err 130 } 131 if !exists { 132 res, err := e.CreateIndex(e.index).Do(context.Background()) 133 if (res != nil && !res.Acknowledged) || err != nil { 134 return fmt.Errorf("failed to create elastic mapping (ack: %v): %v", res != nil && res.Acknowledged, err) 135 } 136 } 137 // mappings updated according to https://www.elastic.co/blog/strings-are-dead-long-live-strings 138 stringNA := map[string]interface{}{ 139 "type": "text", 140 "index": true, 141 } 142 stringA := map[string]interface{}{ 143 "type": "text", 144 "index": true, 145 } 146 date := map[string]string{ 147 "type": "date", 148 } 149 p := make(map[string]interface{}) 150 p[annotate.Message] = stringA 151 p[annotate.StartDate] = date 152 p[annotate.EndDate] = date 153 p[annotate.Source] = stringNA 154 p[annotate.Host] = stringNA 155 p[annotate.CreationUser] = stringNA 156 p[annotate.Owner] = stringNA 157 p[annotate.Category] = stringNA 158 p[annotate.Url] = stringNA 159 mapping := make(map[string]interface{}) 160 mapping["properties"] = p 161 q := e.PutMapping().Index(e.index).Type(docType).BodyJson(mapping) 162 res, err := q.Do(context.Background()) 163 if (res != nil && !res.Acknowledged) || err != nil { 164 return fmt.Errorf("failed to create elastic mapping (ack: %v): %v", res != nil && res.Acknowledged, err) 165 } 166 e.initialized = true 167 return nil 168 } 169 170 func (e *Elastic6) InsertAnnotation(a *annotate.Annotation) error { 171 if !e.initialized { 172 return unInitErr 173 } 174 _, err := e.Index().Index(e.index).BodyJson(a).Id(a.Id).Type(docType).Do(context.Background()) 175 return err 176 } 177 178 func (e *Elastic6) GetAnnotation(id string) (*annotate.Annotation, bool, error) { 179 if !e.initialized { 180 return nil, false, unInitErr 181 } 182 a := annotate.Annotation{} 183 if id == "" { 184 return &a, false, fmt.Errorf("must provide id") 185 } 186 res, err := e.Get().Index(e.index).Type(docType).Id(id).Do(context.Background()) 187 // Ewwww... 188 if err != nil && strings.Contains(err.Error(), "Error 404") { 189 return &a, false, nil 190 } 191 if err != nil { 192 return &a, false, err 193 } 194 if err := json.Unmarshal(*res.Source, &a); err != nil { 195 return &a, res.Found, err 196 } 197 return &a, res.Found, nil 198 } 199 200 func (e *Elastic6) DeleteAnnotation(id string) error { 201 if !e.initialized { 202 return unInitErr 203 } 204 _, err := e.Delete().Index(e.index).Type(docType).Id(id).Do(context.Background()) 205 if err != nil { 206 return err 207 } 208 return nil 209 //TODO? Check res.Found? 210 }