Skip to content

Commit

Permalink
Merge #7984: Data sources for AWS and Fastly IP address ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
apparentlymart committed Aug 9, 2016
2 parents d63008e + 8ea400b commit c6e8662
Show file tree
Hide file tree
Showing 11 changed files with 536 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ FEATURES:
* **New Resource:** `aws_load_balancer_policy` [GH-7458]
* **New Resource:** `aws_load_balancer_backend_server_policy` [GH-7458]
* **New Resource:** `aws_load_balancer_listener_policy` [GH-7458]
* **New Data Source:** `aws_ip_ranges` [GH-7984]
* **New Data Source:** `fastly_ip_ranges` [GH-7984]

IMPROVEMENTS
* provider/aws: Introduce `aws_elasticsearch_domain` `elasticsearch_version` field (to specify ES version) [GH-7860]
Expand Down
151 changes: 151 additions & 0 deletions builtin/providers/aws/data_source_aws_ip_ranges.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package aws

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"sort"
"strconv"
"strings"

"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/terraform/helper/schema"
)

type dataSourceAwsIPRangesResult struct {
CreateDate string
Prefixes []dataSourceAwsIPRangesPrefix
SyncToken string
}

type dataSourceAwsIPRangesPrefix struct {
IpPrefix string `json:"ip_prefix"`
Region string
Service string
}

func dataSourceAwsIPRanges() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsIPRangesRead,

Schema: map[string]*schema.Schema{
"cidr_blocks": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"create_date": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"regions": &schema.Schema{
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"services": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"sync_token": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
},
},
}
}

func dataSourceAwsIPRangesRead(d *schema.ResourceData, meta interface{}) error {

conn := cleanhttp.DefaultClient()

log.Printf("[DEBUG] Reading IP ranges")

res, err := conn.Get("https://ip-ranges.amazonaws.com/ip-ranges.json")

if err != nil {
return fmt.Errorf("Error listing IP ranges: %s", err)
}

defer res.Body.Close()

data, err := ioutil.ReadAll(res.Body)

if err != nil {
return fmt.Errorf("Error reading response body: %s", err)
}

result := new(dataSourceAwsIPRangesResult)

if err := json.Unmarshal(data, result); err != nil {
return fmt.Errorf("Error parsing result: %s", err)
}

if err := d.Set("create_date", result.CreateDate); err != nil {
return fmt.Errorf("Error setting create date: %s", err)
}

syncToken, err := strconv.Atoi(result.SyncToken)

if err != nil {
return fmt.Errorf("Error while converting sync token: %s", err)
}

d.SetId(result.SyncToken)

if err := d.Set("sync_token", syncToken); err != nil {
return fmt.Errorf("Error setting sync token: %s", err)
}

get := func(key string) *schema.Set {

set := d.Get(key).(*schema.Set)

for _, e := range set.List() {

s := e.(string)

set.Remove(s)
set.Add(strings.ToLower(s))

}

return set

}

var (
regions = get("regions")
services = get("services")
noRegionFilter = regions.Len() == 0
prefixes []string
)

for _, e := range result.Prefixes {

var (
matchRegion = noRegionFilter || regions.Contains(strings.ToLower(e.Region))
matchService = services.Contains(strings.ToLower(e.Service))
)

if matchRegion && matchService {
prefixes = append(prefixes, e.IpPrefix)
}

}

if len(prefixes) == 0 {
return fmt.Errorf(" No IP ranges result from filters")
}

sort.Strings(prefixes)

if err := d.Set("cidr_blocks", prefixes); err != nil {
return fmt.Errorf("Error setting ip ranges: %s", err)
}

return nil

}
128 changes: 128 additions & 0 deletions builtin/providers/aws/data_source_aws_ip_ranges_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package aws

import (
"fmt"
"net"
"regexp"
"sort"
"strconv"
"testing"
"time"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSIPRanges(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSIPRangesConfig,
Check: resource.ComposeTestCheckFunc(
testAccAWSIPRanges("data.aws_ip_ranges.some"),
),
},
},
})
}

func testAccAWSIPRanges(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {

r := s.RootModule().Resources[n]
a := r.Primary.Attributes

var (
cidrBlockSize int
createDate time.Time
err error
syncToken int
)

if cidrBlockSize, err = strconv.Atoi(a["cidr_blocks.#"]); err != nil {
return err
}

if cidrBlockSize < 10 {
return fmt.Errorf("cidr_blocks for eu-west-1 seem suspiciously low: %d", cidrBlockSize)
}

if createDate, err = time.Parse("2006-01-02-15-04-05", a["create_date"]); err != nil {
return err
}

if syncToken, err = strconv.Atoi(a["sync_token"]); err != nil {
return err
}

if syncToken != int(createDate.Unix()) {
return fmt.Errorf("sync_token %d does not match create_date %s", syncToken, createDate)
}

var cidrBlocks sort.StringSlice = make([]string, cidrBlockSize)

for i := range make([]string, cidrBlockSize) {

block := a[fmt.Sprintf("cidr_blocks.%d", i)]

if _, _, err := net.ParseCIDR(block); err != nil {
return fmt.Errorf("malformed CIDR block %s: %s", block, err)
}

cidrBlocks[i] = block

}

if !sort.IsSorted(cidrBlocks) {
return fmt.Errorf("unexpected order of cidr_blocks: %s", cidrBlocks)
}

var (
regionMember = regexp.MustCompile(`regions\.\d+`)
regions, services int
serviceMember = regexp.MustCompile(`services\.\d+`)
)

for k, v := range a {

if regionMember.MatchString(k) {

if !(v == "eu-west-1" || v == "EU-central-1") {
return fmt.Errorf("unexpected region %s", v)
}

regions = regions + 1

}

if serviceMember.MatchString(k) {

if v != "EC2" {
return fmt.Errorf("unexpected service %s", v)
}

services = services + 1
}

}

if regions != 2 {
return fmt.Errorf("unexpected number of regions: %d", regions)
}

if services != 1 {
return fmt.Errorf("unexpected number of services: %d", services)
}

return nil
}
}

const testAccAWSIPRangesConfig = `
data "aws_ip_ranges" "some" {
regions = [ "eu-west-1", "EU-central-1" ]
services = [ "EC2" ]
}
`
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ func Provider() terraform.ResourceProvider {
"aws_ami": dataSourceAwsAmi(),
"aws_availability_zones": dataSourceAwsAvailabilityZones(),
"aws_iam_policy_document": dataSourceAwsIamPolicyDocument(),
"aws_ip_ranges": dataSourceAwsIPRanges(),
"aws_s3_bucket_object": dataSourceAwsS3BucketObject(),
"aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(),
},
Expand Down
70 changes: 70 additions & 0 deletions builtin/providers/fastly/data_source_ip_ranges.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package fastly

import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"sort"
"strconv"

"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)

type dataSourceFastlyIPRangesResult struct {
Addresses []string
}

func dataSourceFastlyIPRanges() *schema.Resource {
return &schema.Resource{
Read: dataSourceFastlyIPRangesRead,

Schema: map[string]*schema.Schema{
"cidr_blocks": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}

func dataSourceFastlyIPRangesRead(d *schema.ResourceData, meta interface{}) error {

conn := cleanhttp.DefaultClient()

log.Printf("[DEBUG] Reading IP ranges")

res, err := conn.Get("https://api.fastly.com/public-ip-list")

if err != nil {
return fmt.Errorf("Error listing IP ranges: %s", err)
}

defer res.Body.Close()

data, err := ioutil.ReadAll(res.Body)

if err != nil {
return fmt.Errorf("Error reading response body: %s", err)
}

d.SetId(strconv.Itoa(hashcode.String(string(data))))

result := new(dataSourceFastlyIPRangesResult)

if err := json.Unmarshal(data, result); err != nil {
return fmt.Errorf("Error parsing result: %s", err)
}

sort.Strings(result.Addresses)

if err := d.Set("cidr_blocks", result.Addresses); err != nil {
return fmt.Errorf("Error setting ip ranges: %s", err)
}

return nil

}
Loading

0 comments on commit c6e8662

Please sign in to comment.