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..15dc51c6d --- /dev/null +++ b/builtin/providers/cloudflare/validators.go @@ -0,0 +1,51 @@ +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 "A", "AAAA", "CNAME", "TXT", "SRV", "LOC", "MX", "NS" or "SPF"`, k, value)) + } + 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) + } + } +}