From 8ac7f53c3323270e0339f31e726b449341035dcd Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Fri, 13 Jan 2017 14:42:13 -0500 Subject: [PATCH] provider/cloudflare: Add validation for record types and record content Adds a validation function for cloudflare record types. Also adds an apply-time validation for the record's content based on record type. Currently only validating `A` and `AAAA` records, can be expanded to verify record content for every possible record type in the future. ``` $ make test TEST=./builtin/providers/cloudflare ==> Checking that code complies with gofmt requirements... ==> Checking AWS provider for unchecked errors... ==> NOTE: at this time we only look for uncheck errors in the AWS package ==> Installing errcheck... go generate $(go list ./... | grep -v /terraform/vendor/) 2017/01/13 14:41:37 Generated command/internal_plugin_list.go TF_ACC= go test ./builtin/providers/cloudflare -timeout=30s -parallel=4 ok github.com/hashicorp/terraform/builtin/providers/cloudflare 0.018s ``` Fixes: #11173 --- .../cloudflare/resource_cloudflare_record.go | 12 +++- builtin/providers/cloudflare/validators.go | 52 ++++++++++++++++ .../providers/cloudflare/validators_test.go | 61 +++++++++++++++++++ 3 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 builtin/providers/cloudflare/validators.go create mode 100644 builtin/providers/cloudflare/validators_test.go diff --git a/builtin/providers/cloudflare/resource_cloudflare_record.go b/builtin/providers/cloudflare/resource_cloudflare_record.go index 58b680946..bf9e95fcf 100644 --- a/builtin/providers/cloudflare/resource_cloudflare_record.go +++ b/builtin/providers/cloudflare/resource_cloudflare_record.go @@ -34,9 +34,10 @@ func resourceCloudFlareRecord() *schema.Resource { }, "type": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateRecordType, }, "value": &schema.Schema{ @@ -88,6 +89,11 @@ func resourceCloudFlareRecordCreate(d *schema.ResourceData, meta interface{}) er newRecord.TTL = ttl.(int) } + // Validate value based on type + if err := validateRecordName(newRecord.Type, newRecord.Content); err != nil { + return fmt.Errorf("Error validating record name %q: %s", newRecord.Name, err) + } + zoneId, err := client.ZoneIDByName(newRecord.ZoneName) if err != nil { return fmt.Errorf("Error finding zone %q: %s", newRecord.ZoneName, err) diff --git a/builtin/providers/cloudflare/validators.go b/builtin/providers/cloudflare/validators.go new file mode 100644 index 000000000..4757bbc0f --- /dev/null +++ b/builtin/providers/cloudflare/validators.go @@ -0,0 +1,52 @@ +package cloudflare + +import ( + "fmt" + "net" + "strings" +) + +// validateRecordType ensures that the cloudflare record type is valid +func validateRecordType(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + validTypes := map[string]struct{}{ + "A": {}, + "AAAA": {}, + "CNAME": {}, + "TXT": {}, + "SRV": {}, + "LOC": {}, + "MX": {}, + "NS": {}, + "SPF": {}, + } + + if _, ok := validTypes[value]; !ok { + errors = append(errors, fmt.Errorf( + "%q contains an invalid type %q. Valid types are: %q, %q, %q, %q, %q, %q, %q, %q, or %q", + k, value, "A", "AAAA", "CNAME", "TXT", "SRV", "LOC", "MX", "NS", "SPF")) + } + return +} + +// validateRecordName ensures that based on supplied record type, the name content matches +// Currently only validates A and AAAA types +func validateRecordName(t string, value string) error { + switch t { + case "A": + // Must be ipv4 addr + addr := net.ParseIP(value) + if addr == nil || !strings.Contains(value, ".") { + return fmt.Errorf("A record must be a valid IPv4 address, got: %q", value) + } + case "AAAA": + // Must be ipv6 addr + addr := net.ParseIP(value) + if addr == nil || !strings.Contains(value, ":") { + return fmt.Errorf("AAAA record must be a valid IPv6 address, got: %q", value) + } + } + + return nil +} diff --git a/builtin/providers/cloudflare/validators_test.go b/builtin/providers/cloudflare/validators_test.go new file mode 100644 index 000000000..0c97b18dc --- /dev/null +++ b/builtin/providers/cloudflare/validators_test.go @@ -0,0 +1,61 @@ +package cloudflare + +import "testing" + +func TestValidateRecordType(t *testing.T) { + validTypes := []string{ + "A", + "AAAA", + "CNAME", + "TXT", + "SRV", + "LOC", + "MX", + "NS", + "SPF", + } + for _, v := range validTypes { + _, errors := validateRecordType(v, "type") + if len(errors) != 0 { + t.Fatalf("%q should be a valid record type: %q", v, errors) + } + } + + invalidTypes := []string{ + "a", + "cName", + "txt", + "SRv", + "foo", + "bar", + } + for _, v := range invalidTypes { + _, errors := validateRecordType(v, "type") + if len(errors) == 0 { + t.Fatalf("%q should be an invalid record type", v) + } + } +} + +func TestValidateRecordName(t *testing.T) { + validNames := map[string]string{ + "A": "192.168.0.1", + "AAAA": "2001:0db8:0000:0042:0000:8a2e:0370:7334", + } + + for k, v := range validNames { + if err := validateRecordName(k, v); err != nil { + t.Fatalf("%q should be a valid name for type %q: %v", v, k, err) + } + } + + invalidNames := map[string]string{ + "A": "terraform.io", + "AAAA": "192.168.0.1", + } + for k, v := range invalidNames { + if err := validateRecordName(k, v); err == nil { + t.Fatalf("%q should be an invalid name for type %q", v, k) + } + } +}