provider/aws: utilities for building EC2 filter sets
These functions can be used within various EC2 data sources to support querying by filter. The following cases are supported: - Filtering by exact equality with single attribute values - Filtering by EC2 tag key/value pairs - Explicitly specifying raw EC2 filters in config This should cover most of the filter use-cases for Terraform data sources that are built on EC2's 'Describe...' family of functions.
This commit is contained in:
parent
ab29eca045
commit
de51398b39
|
@ -0,0 +1,153 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
// buildEC2AttributeFilterList takes a flat map of scalar attributes (most
|
||||
// likely values extracted from a *schema.ResourceData on an EC2-querying
|
||||
// data source) and produces a []*ec2.Filter representing an exact match
|
||||
// for each of the given non-empty attributes.
|
||||
//
|
||||
// The keys of the given attributes map are the attribute names expected
|
||||
// by the EC2 API, which are usually either in camelcase or with dash-separated
|
||||
// words. We conventionally map these to underscore-separated identifiers
|
||||
// with the same words when presenting these as data source query attributes
|
||||
// in Terraform.
|
||||
//
|
||||
// It's the callers responsibility to transform any non-string values into
|
||||
// the appropriate string serialization required by the AWS API when
|
||||
// encoding the given filter. Any attributes given with empty string values
|
||||
// are ignored, assuming that the user wishes to leave that attribute
|
||||
// unconstrained while filtering.
|
||||
//
|
||||
// The purpose of this function is to create values to pass in
|
||||
// for the "Filters" attribute on most of the "Describe..." API functions in
|
||||
// the EC2 API, to aid in the implementation of Terraform data sources that
|
||||
// retrieve data about EC2 objects.
|
||||
func buildEC2AttributeFilterList(attrs map[string]string) []*ec2.Filter {
|
||||
filters := make([]*ec2.Filter, 0, len(attrs))
|
||||
|
||||
for filterName, value := range attrs {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
filters = append(filters, &ec2.Filter{
|
||||
Name: aws.String(filterName),
|
||||
Values: []*string{aws.String(value)},
|
||||
})
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
||||
|
||||
// buildEC2TagFilterList takes a []*ec2.Tag and produces a []*ec2.Filter that
|
||||
// represents exact matches for all of the tag key/value pairs given in
|
||||
// the tag set.
|
||||
//
|
||||
// The purpose of this function is to create values to pass in for
|
||||
// the "Filters" attribute on most of the "Describe..." API functions
|
||||
// in the EC2 API, to implement filtering by tag values e.g. in Terraform
|
||||
// data sources that retrieve data about EC2 objects.
|
||||
//
|
||||
// It is conventional for an EC2 data source to include an attribute called
|
||||
// "tags" which conforms to the schema returned by the tagsSchema() function.
|
||||
// The value of this can then be converted to a tags slice using tagsFromMap,
|
||||
// and the result finally passed in to this function.
|
||||
//
|
||||
// In Terraform configuration this would then look like this, to constrain
|
||||
// results by name:
|
||||
//
|
||||
// tags {
|
||||
// Name = "my-awesome-subnet"
|
||||
// }
|
||||
func buildEC2TagFilterList(tags []*ec2.Tag) []*ec2.Filter {
|
||||
filters := make([]*ec2.Filter, len(tags))
|
||||
|
||||
for i, tag := range tags {
|
||||
filters[i] = &ec2.Filter{
|
||||
Name: aws.String(fmt.Sprintf("tag:%s", *tag.Key)),
|
||||
Values: []*string{tag.Value},
|
||||
}
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
||||
|
||||
// ec2CustomFiltersSchema returns a *schema.Schema that represents
|
||||
// a set of custom filtering criteria that a user can specify as input
|
||||
// to a data source that wraps one of the many "Describe..." API calls
|
||||
// in the EC2 API.
|
||||
//
|
||||
// It is conventional for an attribute of this type to be included
|
||||
// as a top-level attribute called "filter". This is the "catch all" for
|
||||
// filter combinations that are not possible to express using scalar
|
||||
// attributes or tags. In Terraform configuration, the custom filter blocks
|
||||
// then look like this:
|
||||
//
|
||||
// filter {
|
||||
// name = "availabilityZone"
|
||||
// values = ["us-west-2a", "us-west-2b"]
|
||||
// }
|
||||
func ec2CustomFiltersSchema() *schema.Schema {
|
||||
return &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"values": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Required: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// buildEC2CustomFilterList takes the set value extracted from a schema
|
||||
// attribute conforming to the schema returned by ec2CustomFiltersSchema,
|
||||
// and transforms it into a []*ec2.Filter representing the same filter
|
||||
// expressions which is ready to pass into the "Filters" attribute on most
|
||||
// of the "Describe..." functions in the EC2 API.
|
||||
//
|
||||
// This function is intended only to be used in conjunction with
|
||||
// ec2CustomFitlersSchema. See the docs on that function for more details
|
||||
// on the configuration pattern this is intended to support.
|
||||
func buildEC2CustomFilterList(filterSet *schema.Set) []*ec2.Filter {
|
||||
if filterSet == nil {
|
||||
return []*ec2.Filter{}
|
||||
}
|
||||
|
||||
customFilters := filterSet.List()
|
||||
filters := make([]*ec2.Filter, len(customFilters))
|
||||
|
||||
for filterIdx, customFilterI := range customFilters {
|
||||
customFilterMapI := customFilterI.(map[string]interface{})
|
||||
name := customFilterMapI["name"].(string)
|
||||
valuesI := customFilterMapI["values"].(*schema.Set).List()
|
||||
values := make([]*string, len(valuesI))
|
||||
for valueIdx, valueI := range valuesI {
|
||||
values[valueIdx] = aws.String(valueI.(string))
|
||||
}
|
||||
|
||||
filters[filterIdx] = &ec2.Filter{
|
||||
Name: &name,
|
||||
Values: values,
|
||||
}
|
||||
}
|
||||
|
||||
return filters
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package aws
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func TestBuildEC2AttributeFilterList(t *testing.T) {
|
||||
type TestCase struct {
|
||||
Attrs map[string]string
|
||||
Expected []*ec2.Filter
|
||||
}
|
||||
testCases := []TestCase{
|
||||
{
|
||||
map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "boo",
|
||||
},
|
||||
[]*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("foo"),
|
||||
Values: []*string{aws.String("bar")},
|
||||
},
|
||||
{
|
||||
Name: aws.String("baz"),
|
||||
Values: []*string{aws.String("boo")},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
map[string]string{
|
||||
"foo": "bar",
|
||||
"baz": "",
|
||||
},
|
||||
[]*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("foo"),
|
||||
Values: []*string{aws.String("bar")},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := buildEC2AttributeFilterList(testCase.Attrs)
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.Expected) {
|
||||
t.Errorf(
|
||||
"test case %d: got %#v, but want %#v",
|
||||
i, result, testCase.Expected,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEC2TagFilterList(t *testing.T) {
|
||||
type TestCase struct {
|
||||
Tags []*ec2.Tag
|
||||
Expected []*ec2.Filter
|
||||
}
|
||||
testCases := []TestCase{
|
||||
{
|
||||
[]*ec2.Tag{
|
||||
{
|
||||
Key: aws.String("foo"),
|
||||
Value: aws.String("bar"),
|
||||
},
|
||||
{
|
||||
Key: aws.String("baz"),
|
||||
Value: aws.String("boo"),
|
||||
},
|
||||
},
|
||||
[]*ec2.Filter{
|
||||
{
|
||||
Name: aws.String("tag:foo"),
|
||||
Values: []*string{aws.String("bar")},
|
||||
},
|
||||
{
|
||||
Name: aws.String("tag:baz"),
|
||||
Values: []*string{aws.String("boo")},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result := buildEC2TagFilterList(testCase.Tags)
|
||||
|
||||
if !reflect.DeepEqual(result, testCase.Expected) {
|
||||
t.Errorf(
|
||||
"test case %d: got %#v, but want %#v",
|
||||
i, result, testCase.Expected,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEC2CustomFilterList(t *testing.T) {
|
||||
|
||||
// We need to get a set with the appropriate hash function,
|
||||
// so we'll use the schema to help us produce what would
|
||||
// be produced in the normal case.
|
||||
filtersSchema := ec2CustomFiltersSchema()
|
||||
|
||||
// The zero value of this schema will be an interface{}
|
||||
// referring to a new, empty *schema.Set with the
|
||||
// appropriate hash function configured.
|
||||
filters := filtersSchema.ZeroValue().(*schema.Set)
|
||||
|
||||
// We also need an appropriately-configured set for
|
||||
// the list of values.
|
||||
valuesSchema := filtersSchema.Elem.(*schema.Resource).Schema["values"]
|
||||
valuesSet := func(vals ...string) *schema.Set {
|
||||
ret := valuesSchema.ZeroValue().(*schema.Set)
|
||||
for _, val := range vals {
|
||||
ret.Add(val)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
filters.Add(map[string]interface{}{
|
||||
"name": "foo",
|
||||
"values": valuesSet("bar", "baz"),
|
||||
})
|
||||
filters.Add(map[string]interface{}{
|
||||
"name": "pizza",
|
||||
"values": valuesSet("cheese"),
|
||||
})
|
||||
|
||||
expected := []*ec2.Filter{
|
||||
// These are produced in the deterministic order guaranteed
|
||||
// by schema.Set.List(), which happens to produce them in
|
||||
// the following order for our current input. If this test
|
||||
// evolves with different input data in future then they
|
||||
// will likely be emitted in a different order, which is fine.
|
||||
{
|
||||
Name: aws.String("pizza"),
|
||||
Values: []*string{aws.String("cheese")},
|
||||
},
|
||||
{
|
||||
Name: aws.String("foo"),
|
||||
Values: []*string{aws.String("bar"), aws.String("baz")},
|
||||
},
|
||||
}
|
||||
result := buildEC2CustomFilterList(filters)
|
||||
|
||||
if !reflect.DeepEqual(result, expected) {
|
||||
t.Errorf(
|
||||
"got %#v, but want %#v",
|
||||
result, expected,
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue