Merge pull request #8578 from kwilczynski/feature/health-check-target-validation-aws_elb

provider/aws: Add validation of Health Check target to aws_elb.
This commit is contained in:
Paul Stack 2016-09-04 00:18:19 +03:00 committed by GitHub
commit 9ad4e8453b
2 changed files with 323 additions and 24 deletions

View File

@ -4,6 +4,8 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"log" "log"
"regexp"
"strconv"
"strings" "strings"
"time" "time"
@ -95,6 +97,7 @@ func resourceAwsElb() *schema.Resource {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
Default: 60, Default: 60,
ValidateFunc: validateIntegerInRange(1, 3600),
}, },
"connection_draining": &schema.Schema{ "connection_draining": &schema.Schema{
@ -118,6 +121,7 @@ func resourceAwsElb() *schema.Resource {
Type: schema.TypeInt, Type: schema.TypeInt,
Optional: true, Optional: true,
Default: 60, Default: 60,
ValidateFunc: validateAccessLogsInterval,
}, },
"bucket": &schema.Schema{ "bucket": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
@ -144,21 +148,25 @@ func resourceAwsElb() *schema.Resource {
"instance_port": &schema.Schema{ "instance_port": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
ValidateFunc: validateIntegerInRange(1, 65535),
}, },
"instance_protocol": &schema.Schema{ "instance_protocol": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ValidateFunc: validateListenerProtocol,
}, },
"lb_port": &schema.Schema{ "lb_port": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
ValidateFunc: validateIntegerInRange(1, 65535),
}, },
"lb_protocol": &schema.Schema{ "lb_protocol": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ValidateFunc: validateListenerProtocol,
}, },
"ssl_certificate_id": &schema.Schema{ "ssl_certificate_id": &schema.Schema{
@ -180,26 +188,31 @@ func resourceAwsElb() *schema.Resource {
"healthy_threshold": &schema.Schema{ "healthy_threshold": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
ValidateFunc: validateIntegerInRange(2, 10),
}, },
"unhealthy_threshold": &schema.Schema{ "unhealthy_threshold": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
ValidateFunc: validateIntegerInRange(2, 10),
}, },
"target": &schema.Schema{ "target": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Required: true,
ValidateFunc: validateHeathCheckTarget,
}, },
"interval": &schema.Schema{ "interval": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
ValidateFunc: validateIntegerInRange(5, 300),
}, },
"timeout": &schema.Schema{ "timeout": &schema.Schema{
Type: schema.TypeInt, Type: schema.TypeInt,
Required: true, Required: true,
ValidateFunc: validateIntegerInRange(2, 60),
}, },
}, },
}, },
@ -807,3 +820,112 @@ func sourceSGIdByName(meta interface{}, sg, vpcId string) (string, error) {
group := resp.SecurityGroups[0] group := resp.SecurityGroups[0]
return *group.GroupId, nil return *group.GroupId, nil
} }
func validateAccessLogsInterval(v interface{}, k string) (ws []string, errors []error) {
value := v.(int)
// Check if the value is either 5 or 60 (minutes).
if value != 5 && value != 60 {
errors = append(errors, fmt.Errorf(
"%q contains an invalid Access Logs interval \"%d\". "+
"Valid intervals are either 5 or 60 (minutes).",
k, value))
}
return
}
func validateHeathCheckTarget(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
// Parse the Health Check target value.
matches := regexp.MustCompile(`\A(\w+):(\d+)(.+)?\z`).FindStringSubmatch(value)
// Check if the value contains a valid target.
if matches == nil || len(matches) < 1 {
errors = append(errors, fmt.Errorf(
"%q contains an invalid Health Check: %s",
k, value))
// Invalid target? Return immediately,
// there is no need to collect other
// errors.
return
}
// Check if the value contains a valid protocol.
if !isValidProtocol(matches[1]) {
errors = append(errors, fmt.Errorf(
"%q contains an invalid Health Check protocol %q. "+
"Valid protocols are either %q, %q, %q, or %q.",
k, matches[1], "TCP", "SSL", "HTTP", "HTTPS"))
}
// Check if the value contains a valid port range.
port, _ := strconv.Atoi(matches[2])
if port < 1 || port > 65535 {
errors = append(errors, fmt.Errorf(
"%q contains an invalid Health Check target port \"%d\". "+
"Valid port is in the range from 1 to 65535 inclusive.",
k, port))
}
switch strings.ToLower(matches[1]) {
case "tcp", "ssl":
// Check if value is in the form <PROTOCOL>:<PORT> for TCP and/or SSL.
if matches[3] != "" {
errors = append(errors, fmt.Errorf(
"%q cannot contain a path in the Health Check target: %s",
k, value))
}
break
case "http", "https":
// Check if value is in the form <PROTOCOL>:<PORT>/<PATH> for HTTP and/or HTTPS.
if matches[3] == "" {
errors = append(errors, fmt.Errorf(
"%q must contain a path in the Health Check target: %s",
k, value))
}
// Cannot be longer than 1024 multibyte characters.
if len([]rune(matches[3])) > 1024 {
errors = append(errors, fmt.Errorf("%q cannot contain a path longer "+
"than 1024 characters in the Health Check target: %s",
k, value))
}
break
}
return
}
func validateListenerProtocol(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !isValidProtocol(value) {
errors = append(errors, fmt.Errorf(
"%q contains an invalid Listener protocol %q. "+
"Valid protocols are either %q, %q, %q, or %q.",
k, value, "TCP", "SSL", "HTTP", "HTTPS"))
}
return
}
func isValidProtocol(s string) bool {
if s == "" {
return false
}
s = strings.ToLower(s)
validProtocols := map[string]bool{
"http": true,
"https": true,
"ssl": true,
"tcp": true,
}
if _, ok := validProtocols[s]; !ok {
return false
}
return true
}

View File

@ -599,6 +599,183 @@ func TestResourceAWSELB_validateElbNameCannotEndWithHyphen(t *testing.T) {
} }
} }
func TestResourceAWSELB_validateAccessLogsInterval(t *testing.T) {
type testCases struct {
Value int
ErrCount int
}
invalidCases := []testCases{
{
Value: 0,
ErrCount: 1,
},
{
Value: 10,
ErrCount: 1,
},
{
Value: -1,
ErrCount: 1,
},
}
for _, tc := range invalidCases {
_, errors := validateAccessLogsInterval(tc.Value, "interval")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q to trigger a validation error.", tc.Value)
}
}
}
func TestResourceAWSELB_validateListenerProtocol(t *testing.T) {
type testCases struct {
Value string
ErrCount int
}
invalidCases := []testCases{
{
Value: "",
ErrCount: 1,
},
{
Value: "incorrect",
ErrCount: 1,
},
{
Value: "HTTP:",
ErrCount: 1,
},
}
for _, tc := range invalidCases {
_, errors := validateListenerProtocol(tc.Value, "protocol")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q to trigger a validation error.", tc.Value)
}
}
validCases := []testCases{
{
Value: "TCP",
ErrCount: 0,
},
{
Value: "ssl",
ErrCount: 0,
},
{
Value: "HTTP",
ErrCount: 0,
},
}
for _, tc := range validCases {
_, errors := validateListenerProtocol(tc.Value, "protocol")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q not to trigger a validation error.", tc.Value)
}
}
}
func TestResourceAWSELB_validateHealthCheckTarget(t *testing.T) {
type testCase struct {
Value string
ErrCount int
}
randomRunes := func(n int) string {
rand.Seed(time.Now().UTC().UnixNano())
// A complete set of modern Katakana characters.
runes := []rune("アイウエオ" +
"カキクケコガギグゲゴサシスセソザジズゼゾ" +
"タチツテトダヂヅデドナニヌネノハヒフヘホ" +
"バビブベボパピプペポマミムメモヤユヨラリ" +
"ルレロワヰヱヲン")
s := make([]rune, n)
for i := range s {
s[i] = runes[rand.Intn(len(runes))]
}
return string(s)
}
validCases := []testCase{
{
Value: "TCP:1234",
ErrCount: 0,
},
{
Value: "http:80/test",
ErrCount: 0,
},
{
Value: fmt.Sprintf("HTTP:8080/%s", randomRunes(5)),
ErrCount: 0,
},
{
Value: "SSL:8080",
ErrCount: 0,
},
}
for _, tc := range validCases {
_, errors := validateHeathCheckTarget(tc.Value, "target")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q not to trigger a validation error.", tc.Value)
}
}
invalidCases := []testCase{
{
Value: "",
ErrCount: 1,
},
{
Value: "TCP:",
ErrCount: 1,
},
{
Value: "TCP:1234/",
ErrCount: 1,
},
{
Value: "SSL:8080/",
ErrCount: 1,
},
{
Value: "HTTP:8080",
ErrCount: 1,
},
{
Value: "incorrect-value",
ErrCount: 1,
},
{
Value: "TCP:123456",
ErrCount: 1,
},
{
Value: "incorrect:80/",
ErrCount: 1,
},
{
Value: fmt.Sprintf("HTTP:8080/%s%s", randomString(512), randomRunes(512)),
ErrCount: 1,
},
}
for _, tc := range invalidCases {
_, errors := validateHeathCheckTarget(tc.Value, "target")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected %q to trigger a validation error.", tc.Value)
}
}
}
func testAccCheckAWSELBDestroy(s *terraform.State) error { func testAccCheckAWSELBDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).elbconn conn := testAccProvider.Meta().(*AWSClient).elbconn