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:
commit
9ad4e8453b
|
@ -4,6 +4,8 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -92,9 +94,10 @@ func resourceAwsElb() *schema.Resource {
|
|||
},
|
||||
|
||||
"idle_timeout": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 60,
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 60,
|
||||
ValidateFunc: validateIntegerInRange(1, 3600),
|
||||
},
|
||||
|
||||
"connection_draining": &schema.Schema{
|
||||
|
@ -115,9 +118,10 @@ func resourceAwsElb() *schema.Resource {
|
|||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"interval": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 60,
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 60,
|
||||
ValidateFunc: validateAccessLogsInterval,
|
||||
},
|
||||
"bucket": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
|
@ -142,23 +146,27 @@ func resourceAwsElb() *schema.Resource {
|
|||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"instance_port": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validateIntegerInRange(1, 65535),
|
||||
},
|
||||
|
||||
"instance_protocol": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: validateListenerProtocol,
|
||||
},
|
||||
|
||||
"lb_port": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validateIntegerInRange(1, 65535),
|
||||
},
|
||||
|
||||
"lb_protocol": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: validateListenerProtocol,
|
||||
},
|
||||
|
||||
"ssl_certificate_id": &schema.Schema{
|
||||
|
@ -178,28 +186,33 @@ func resourceAwsElb() *schema.Resource {
|
|||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"healthy_threshold": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validateIntegerInRange(2, 10),
|
||||
},
|
||||
|
||||
"unhealthy_threshold": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validateIntegerInRange(2, 10),
|
||||
},
|
||||
|
||||
"target": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: validateHeathCheckTarget,
|
||||
},
|
||||
|
||||
"interval": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validateIntegerInRange(5, 300),
|
||||
},
|
||||
|
||||
"timeout": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
ValidateFunc: validateIntegerInRange(2, 60),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -807,3 +820,112 @@ func sourceSGIdByName(meta interface{}, sg, vpcId string) (string, error) {
|
|||
group := resp.SecurityGroups[0]
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
conn := testAccProvider.Meta().(*AWSClient).elbconn
|
||||
|
||||
|
|
Loading…
Reference in New Issue