Skip to content

Commit 17485ea

Browse files
committed
Add IPRange aggregation
This commit adds the IP range aggregation type described in [1]. To access the buckets in response, use `IPRange`. [1] https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-bucket-iprange-aggregation.html
1 parent 30c7022 commit 17485ea

4 files changed

+289
-4
lines changed

search_aggs.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,9 @@ func (a Aggregations) DateRange(name string) (*AggregationBucketRangeItems, bool
398398
return nil, false
399399
}
400400

401-
// IPv4Range returns IPv4 range aggregation results.
401+
// IPRange returns IP range aggregation results.
402402
// See: https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-bucket-iprange-aggregation.html
403-
func (a Aggregations) IPv4Range(name string) (*AggregationBucketRangeItems, bool) {
403+
func (a Aggregations) IPRange(name string) (*AggregationBucketRangeItems, bool) {
404404
if raw, found := a[name]; found {
405405
agg := new(AggregationBucketRangeItems)
406406
if raw == nil {

search_aggs_bucket_ip_range.go

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Copyright 2012-present Oliver Eilhard. All rights reserved.
2+
// Use of this source code is governed by a MIT-license.
3+
// See http://olivere.mit-license.org/license.txt for details.
4+
5+
package elastic
6+
7+
// IPRangeAggregation is a range aggregation that is dedicated for
8+
// IP addresses.
9+
//
10+
// See: https://www.elastic.co/guide/en/elasticsearch/reference/6.0/search-aggregations-bucket-iprange-aggregation.html
11+
type IPRangeAggregation struct {
12+
field string
13+
subAggregations map[string]Aggregation
14+
meta map[string]interface{}
15+
keyed *bool
16+
entries []IPRangeAggregationEntry
17+
}
18+
19+
type IPRangeAggregationEntry struct {
20+
Key string
21+
Mask string
22+
From string
23+
To string
24+
}
25+
26+
func NewIPRangeAggregation() *IPRangeAggregation {
27+
return &IPRangeAggregation{
28+
subAggregations: make(map[string]Aggregation),
29+
entries: make([]IPRangeAggregationEntry, 0),
30+
}
31+
}
32+
33+
func (a *IPRangeAggregation) Field(field string) *IPRangeAggregation {
34+
a.field = field
35+
return a
36+
}
37+
38+
func (a *IPRangeAggregation) SubAggregation(name string, subAggregation Aggregation) *IPRangeAggregation {
39+
a.subAggregations[name] = subAggregation
40+
return a
41+
}
42+
43+
// Meta sets the meta data to be included in the aggregation response.
44+
func (a *IPRangeAggregation) Meta(metaData map[string]interface{}) *IPRangeAggregation {
45+
a.meta = metaData
46+
return a
47+
}
48+
49+
func (a *IPRangeAggregation) Keyed(keyed bool) *IPRangeAggregation {
50+
a.keyed = &keyed
51+
return a
52+
}
53+
54+
func (a *IPRangeAggregation) AddMaskRange(mask string) *IPRangeAggregation {
55+
a.entries = append(a.entries, IPRangeAggregationEntry{Mask: mask})
56+
return a
57+
}
58+
59+
func (a *IPRangeAggregation) AddMaskRangeWithKey(key, mask string) *IPRangeAggregation {
60+
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, Mask: mask})
61+
return a
62+
}
63+
64+
func (a *IPRangeAggregation) AddRange(from, to string) *IPRangeAggregation {
65+
a.entries = append(a.entries, IPRangeAggregationEntry{From: from, To: to})
66+
return a
67+
}
68+
69+
func (a *IPRangeAggregation) AddRangeWithKey(key, from, to string) *IPRangeAggregation {
70+
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: from, To: to})
71+
return a
72+
}
73+
74+
func (a *IPRangeAggregation) AddUnboundedTo(from string) *IPRangeAggregation {
75+
a.entries = append(a.entries, IPRangeAggregationEntry{From: from, To: ""})
76+
return a
77+
}
78+
79+
func (a *IPRangeAggregation) AddUnboundedToWithKey(key, from string) *IPRangeAggregation {
80+
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: from, To: ""})
81+
return a
82+
}
83+
84+
func (a *IPRangeAggregation) AddUnboundedFrom(to string) *IPRangeAggregation {
85+
a.entries = append(a.entries, IPRangeAggregationEntry{From: "", To: to})
86+
return a
87+
}
88+
89+
func (a *IPRangeAggregation) AddUnboundedFromWithKey(key, to string) *IPRangeAggregation {
90+
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: "", To: to})
91+
return a
92+
}
93+
94+
func (a *IPRangeAggregation) Lt(to string) *IPRangeAggregation {
95+
a.entries = append(a.entries, IPRangeAggregationEntry{From: "", To: to})
96+
return a
97+
}
98+
99+
func (a *IPRangeAggregation) LtWithKey(key, to string) *IPRangeAggregation {
100+
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: "", To: to})
101+
return a
102+
}
103+
104+
func (a *IPRangeAggregation) Between(from, to string) *IPRangeAggregation {
105+
a.entries = append(a.entries, IPRangeAggregationEntry{From: from, To: to})
106+
return a
107+
}
108+
109+
func (a *IPRangeAggregation) BetweenWithKey(key, from, to string) *IPRangeAggregation {
110+
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: from, To: to})
111+
return a
112+
}
113+
114+
func (a *IPRangeAggregation) Gt(from string) *IPRangeAggregation {
115+
a.entries = append(a.entries, IPRangeAggregationEntry{From: from, To: ""})
116+
return a
117+
}
118+
119+
func (a *IPRangeAggregation) GtWithKey(key, from string) *IPRangeAggregation {
120+
a.entries = append(a.entries, IPRangeAggregationEntry{Key: key, From: from, To: ""})
121+
return a
122+
}
123+
124+
func (a *IPRangeAggregation) Source() (interface{}, error) {
125+
// Example:
126+
// {
127+
// "aggs" : {
128+
// "range" : {
129+
// "ip_range": {
130+
// "field": "ip",
131+
// "ranges": [
132+
// { "to": "10.0.0.5" },
133+
// { "from": "10.0.0.5" }
134+
// ]
135+
// }
136+
// }
137+
// }
138+
// }
139+
// }
140+
//
141+
// This method returns only the { "ip_range" : { ... } } part.
142+
143+
source := make(map[string]interface{})
144+
opts := make(map[string]interface{})
145+
source["ip_range"] = opts
146+
147+
// ValuesSourceAggregationBuilder
148+
if a.field != "" {
149+
opts["field"] = a.field
150+
}
151+
152+
if a.keyed != nil {
153+
opts["keyed"] = *a.keyed
154+
}
155+
156+
var ranges []interface{}
157+
for _, ent := range a.entries {
158+
r := make(map[string]interface{})
159+
if ent.Key != "" {
160+
r["key"] = ent.Key
161+
}
162+
if ent.Mask != "" {
163+
r["mask"] = ent.Mask
164+
} else {
165+
if ent.From != "" {
166+
r["from"] = ent.From
167+
}
168+
if ent.To != "" {
169+
r["to"] = ent.To
170+
}
171+
}
172+
ranges = append(ranges, r)
173+
}
174+
opts["ranges"] = ranges
175+
176+
// AggregationBuilder (SubAggregations)
177+
if len(a.subAggregations) > 0 {
178+
aggsMap := make(map[string]interface{})
179+
source["aggregations"] = aggsMap
180+
for name, aggregate := range a.subAggregations {
181+
src, err := aggregate.Source()
182+
if err != nil {
183+
return nil, err
184+
}
185+
aggsMap[name] = src
186+
}
187+
}
188+
189+
// Add Meta data if available
190+
if len(a.meta) > 0 {
191+
source["meta"] = a.meta
192+
}
193+
194+
return source, nil
195+
}

search_aggs_bucket_ip_range_test.go

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2012-present Oliver Eilhard. All rights reserved.
2+
// Use of this source code is governed by a MIT-license.
3+
// See http://olivere.mit-license.org/license.txt for details.
4+
5+
package elastic
6+
7+
import (
8+
"encoding/json"
9+
"testing"
10+
)
11+
12+
func TestIPRangeAggregation(t *testing.T) {
13+
agg := NewIPRangeAggregation().Field("remote_ip")
14+
agg = agg.AddRange("", "10.0.0.0")
15+
agg = agg.AddRange("10.1.0.0", "10.1.255.255")
16+
agg = agg.AddRange("10.2.0.0", "")
17+
src, err := agg.Source()
18+
if err != nil {
19+
t.Fatal(err)
20+
}
21+
data, err := json.Marshal(src)
22+
if err != nil {
23+
t.Fatalf("marshaling to JSON failed: %v", err)
24+
}
25+
got := string(data)
26+
expected := `{"ip_range":{"field":"remote_ip","ranges":[{"to":"10.0.0.0"},{"from":"10.1.0.0","to":"10.1.255.255"},{"from":"10.2.0.0"}]}}`
27+
if got != expected {
28+
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
29+
}
30+
}
31+
32+
func TestIPRangeAggregationMask(t *testing.T) {
33+
agg := NewIPRangeAggregation().Field("remote_ip")
34+
agg = agg.AddMaskRange("10.0.0.0/25")
35+
agg = agg.AddMaskRange("10.0.0.127/25")
36+
src, err := agg.Source()
37+
if err != nil {
38+
t.Fatal(err)
39+
}
40+
data, err := json.Marshal(src)
41+
if err != nil {
42+
t.Fatalf("marshaling to JSON failed: %v", err)
43+
}
44+
got := string(data)
45+
expected := `{"ip_range":{"field":"remote_ip","ranges":[{"mask":"10.0.0.0/25"},{"mask":"10.0.0.127/25"}]}}`
46+
if got != expected {
47+
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
48+
}
49+
}
50+
51+
func TestIPRangeAggregationWithKeyedFlag(t *testing.T) {
52+
agg := NewIPRangeAggregation().Field("remote_ip")
53+
agg = agg.Keyed(true)
54+
agg = agg.AddRange("", "10.0.0.0")
55+
agg = agg.AddRange("10.1.0.0", "10.1.255.255")
56+
agg = agg.AddRange("10.2.0.0", "")
57+
src, err := agg.Source()
58+
if err != nil {
59+
t.Fatal(err)
60+
}
61+
data, err := json.Marshal(src)
62+
if err != nil {
63+
t.Fatalf("marshaling to JSON failed: %v", err)
64+
}
65+
got := string(data)
66+
expected := `{"ip_range":{"field":"remote_ip","keyed":true,"ranges":[{"to":"10.0.0.0"},{"from":"10.1.0.0","to":"10.1.255.255"},{"from":"10.2.0.0"}]}}`
67+
if got != expected {
68+
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
69+
}
70+
}
71+
72+
func TestIPRangeAggregationWithKeys(t *testing.T) {
73+
agg := NewIPRangeAggregation().Field("remote_ip")
74+
agg = agg.Keyed(true)
75+
agg = agg.LtWithKey("infinity", "10.0.0.5")
76+
agg = agg.GtWithKey("and-beyond", "10.0.0.5")
77+
src, err := agg.Source()
78+
if err != nil {
79+
t.Fatal(err)
80+
}
81+
data, err := json.Marshal(src)
82+
if err != nil {
83+
t.Fatalf("marshaling to JSON failed: %v", err)
84+
}
85+
got := string(data)
86+
expected := `{"ip_range":{"field":"remote_ip","keyed":true,"ranges":[{"key":"infinity","to":"10.0.0.5"},{"from":"10.0.0.5","key":"and-beyond"}]}}`
87+
if got != expected {
88+
t.Errorf("expected\n%s\n,got:\n%s", expected, got)
89+
}
90+
}

search_aggs_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -2420,7 +2420,7 @@ func TestAggsBucketDateRange(t *testing.T) {
24202420
}
24212421
}
24222422

2423-
func TestAggsBucketIPv4Range(t *testing.T) {
2423+
func TestAggsBucketIPRange(t *testing.T) {
24242424
s := `{
24252425
"ip_ranges": {
24262426
"buckets" : [
@@ -2444,7 +2444,7 @@ func TestAggsBucketIPv4Range(t *testing.T) {
24442444
t.Fatalf("expected no error decoding; got: %v", err)
24452445
}
24462446

2447-
agg, found := aggs.IPv4Range("ip_ranges")
2447+
agg, found := aggs.IPRange("ip_ranges")
24482448
if !found {
24492449
t.Fatalf("expected aggregation to be found; got: %v", found)
24502450
}

0 commit comments

Comments
 (0)