-
Notifications
You must be signed in to change notification settings - Fork 9.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Datasource for AWS and Fastly IP ranges #7984
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
13ea0a0
Added IP ranges from Fastly
yawn 8accef2
Added IP ranges from AWS
yawn e21203a
Changed sync token to int and use it as id.
yawn 67b4b4c
Use content as id.
yawn 2caae49
Fail if filter yields no results.
yawn 9f56528
Renamed blocks to cidr_blocks.
yawn 67bf13f
Added documentation.
yawn d0278ec
Normalize sets, don’t check for missing services.
yawn 8ea400b
Added tests.
yawn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
128
builtin/providers/aws/data_source_aws_ip_ranges_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ] | ||
} | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now, due to some interactions with some core Terraform code that's common to both data and managed resources, it's required to set an id for a data resource but it doesn't really matter what it's set to... so this should be fine as long as
result.SyncToken
is guaranteed to never be the empty string.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we perhaps declare some constant that manifests this fact (e.g.
DefaultDataSourceId
) and start to use that? Might be useful since even in the current code the way IDs are derived or generated vary wildly.