Skip to content

Commit e294002

Browse files
author
ccushing
committed
Filters support bool int and number types
1 parent 99823f5 commit e294002

File tree

3 files changed

+223
-144
lines changed

3 files changed

+223
-144
lines changed

docs/Filters.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
### Data Sources Filtering
22

3-
Most data sources that return lists of resources now support filtering
4-
semantics. To employ a filter include this block in your data source
5-
definition:
3+
Data sources that return lists of resources support filtering semantics.
4+
To employ a filter include this block in your data source definition:
65

76
```
87
filter {
@@ -27,8 +26,7 @@ data "oci_core_shape" "t" {
2726
}
2827
```
2928

30-
Multiple filters blocks can be composed to form **AND** type comparisons
31-
as well. The example below will return a data source containing
29+
Multiple filters blocks can be composed to form **AND** type comparisons. The example below will return a data source containing
3230
_running instances_ in the _first AD_ of a region:
3331
```
3432
data "oci_core_instances" "s" {
@@ -54,10 +52,8 @@ expression special characters need to be escaped with another slash,
5452
shown above as the first `\` before `\w` in `"\\w*-AD-1"`.
5553

5654
### Limitations
57-
Currently filters can only target top level string attributes of a
55+
Currently filters can only target top level attributes of a
5856
resource (or top level arrays of strings).
5957

60-
Support for other types (booleans) and drilling into properties of
61-
structured objects or lists of structured objects is not currently
62-
supported. If these properties are targeted no results will be
63-
returned from the datasource.
58+
Drilling into properties of structured objects or lists of structured objects is not currently
59+
supported. If these properties are targeted no results will be returned from the datasource.

provider/filters.go

Lines changed: 73 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
// Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
2+
13
package provider
24

35
import (
6+
"log"
47
"reflect"
58
"regexp"
6-
7-
"fmt"
9+
"strconv"
810

911
"github.com/hashicorp/terraform/helper/schema"
1012
)
@@ -49,72 +51,95 @@ func ApplyFilters(filters *schema.Set, items []map[string]interface{}) []map[str
4951
keyword := fSet["name"].(string)
5052

5153
isReg := false
52-
if regex, regexOk := fSet["regex"]; regexOk && regex != nil {
53-
if strVal, strValOk := regex.(string); strValOk {
54-
isReg = strVal == "1" || strVal == "true"
55-
} else if boolVal, boolValOk := regex.(bool); boolValOk {
56-
isReg = boolVal
57-
}
54+
if regex, regexOk := fSet["regex"]; regexOk {
55+
isReg = regex.(bool)
5856
}
5957

60-
check := func(filterVal string, propertyVal string) bool {
58+
// create a string equality check strategy based on this filters "regex" flag
59+
stringsEqual := func(propertyVal string, filterVal string) bool {
6160
if isReg {
6261
re, err := regexp.Compile(filterVal)
6362
if err != nil {
64-
panic(fmt.Errorf(`Invalid regular expression "%s" for "%s" filter`, filterVal, keyword))
63+
// todo: when all SetData() fns are refactored to return a possible error, these log statements should
64+
// be converted to errors for return propagation
65+
log.Printf(`[WARN] Invalid regular expression "%s" for "%s" filter\n`, filterVal, keyword)
66+
return false
6567
}
6668
return re.MatchString(propertyVal)
6769
}
6870

6971
return filterVal == propertyVal
7072
}
7173

72-
orComparator := func(item map[string]interface{}) bool {
73-
actualValue, valueExists := item[keyword]
74-
if !valueExists {
75-
return false
74+
// build a collection of items from matches against the set of filters
75+
res := make([]map[string]interface{}, 0)
76+
for _, item := range items {
77+
targetVal, targetValOk := item[keyword]
78+
if targetValOk && orComparator(targetVal, fSet["values"].([]interface{}), stringsEqual) {
79+
res = append(res, item)
7680
}
81+
}
82+
items = res
83+
}
7784

78-
// We use reflection to determine whether the underlying type of the filtering attribute is a string or
79-
// array of strings. Mainly used because the property could be an SDK enum with underlying string type.
80-
// TODO: We should store SDK enum values in state as strings prior to calling ApplyFilters, to avoid using reflection
81-
rValue := reflect.ValueOf(actualValue)
82-
rType := rValue.Type()
85+
return items
86+
}
8387

84-
isStringArray := (rType.Kind() == reflect.Slice || rType.Kind() == reflect.Array) && rType.Elem().Kind() == reflect.String
85-
isString := rType.Kind() == reflect.String
86-
if !isStringArray && !isString {
87-
// property is neither a string nor array of strings, so it can be filtered out
88+
type StringCheck func(propertyVal string, filterVal string) bool
89+
90+
// orComparator returns true for any filter that matches the target property
91+
func orComparator(target interface{}, filters []interface{}, stringsEqual StringCheck) bool {
92+
// Use reflection to determine whether the underlying type of the filtering attribute is a string or
93+
// array of strings. Mainly used because the property could be an SDK enum with underlying string type.
94+
val := reflect.ValueOf(target)
95+
valType := val.Type()
96+
97+
for _, fVal := range filters {
98+
switch valType.Kind() {
99+
case reflect.Bool:
100+
fBool, err := strconv.ParseBool(fVal.(string))
101+
if err != nil {
102+
log.Println("[WARN] Filtering against Type Bool field with un-parsable string boolean form")
88103
return false
89104
}
90-
91-
for _, filterValue := range fSet["values"].([]interface{}) {
92-
if isStringArray {
93-
arrLen := rValue.Len()
94-
for i := 0; i < arrLen; i++ {
95-
if check(filterValue.(string), rValue.Index(i).String()) {
96-
return true
97-
}
105+
if val.Bool() == fBool {
106+
return true
107+
}
108+
case reflect.Int:
109+
// the target field is of type int, but the filter values list element type is string, users can supply string
110+
// or int like `values = [300, "3600"]` but terraform will converts to string, so use ParseInt
111+
fInt, err := strconv.ParseInt(fVal.(string), 10, 64)
112+
if err != nil {
113+
log.Println("[WARN] Filtering against Type Int field with non-int filter value")
114+
return false
115+
}
116+
if val.Int() == fInt {
117+
return true
118+
}
119+
case reflect.Float64:
120+
// same comment as above for Ints
121+
fFloat, err := strconv.ParseFloat(fVal.(string), 64)
122+
if err != nil {
123+
log.Println("[WARN] Filtering against Type Float field with non-float filter value")
124+
return false
125+
}
126+
if val.Float() == fFloat {
127+
return true
128+
}
129+
case reflect.String:
130+
if stringsEqual(val.String(), fVal.(string)) {
131+
return true
132+
}
133+
case reflect.Slice, reflect.Array:
134+
if valType.Elem().Kind() == reflect.String {
135+
arrLen := val.Len()
136+
for i := 0; i < arrLen; i++ {
137+
if stringsEqual(val.Index(i).String(), fVal.(string)) {
138+
return true
98139
}
99-
} else if check(filterValue.(string), rValue.String()) {
100-
return true
101140
}
102141
}
103-
return false
104-
}
105-
106-
items = filter(items, orComparator)
107-
}
108-
109-
return items
110-
}
111-
112-
func filter(items []map[string]interface{}, comparator func(map[string]interface{}) bool) []map[string]interface{} {
113-
res := make([]map[string]interface{}, 0)
114-
for _, item := range items {
115-
if comparator(item) {
116-
res = append(res, item)
117142
}
118143
}
119-
return res
144+
return false
120145
}

0 commit comments

Comments
 (0)