From 5c1dabdb6901a0542962d38aea7f1181c87e4dc9 Mon Sep 17 00:00:00 2001 From: TANABE Ken-ichi Date: Mon, 27 Apr 2015 01:16:52 +0900 Subject: [PATCH 1/5] provider/aws: Add ProxyProtocol support via aws_proxy_protocol_policy --- builtin/providers/aws/provider.go | 1 + .../aws/resource_aws_proxy_protocol_policy.go | 269 ++++++++++++++++++ builtin/providers/aws/structure.go | 13 + 3 files changed, 283 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_proxy_protocol_policy.go diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index 9efc95a04f..60ea61794b 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -96,6 +96,7 @@ func Provider() terraform.ResourceProvider { "aws_main_route_table_association": resourceAwsMainRouteTableAssociation(), "aws_network_acl": resourceAwsNetworkAcl(), "aws_network_interface": resourceAwsNetworkInterface(), + "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_route53_record": resourceAwsRoute53Record(), "aws_route53_zone": resourceAwsRoute53Zone(), "aws_route_table": resourceAwsRouteTable(), diff --git a/builtin/providers/aws/resource_aws_proxy_protocol_policy.go b/builtin/providers/aws/resource_aws_proxy_protocol_policy.go new file mode 100644 index 0000000000..2b988097d3 --- /dev/null +++ b/builtin/providers/aws/resource_aws_proxy_protocol_policy.go @@ -0,0 +1,269 @@ +package aws + +import ( + "fmt" + "log" + "strconv" + "strings" + + "github.com/awslabs/aws-sdk-go/aws" + "github.com/awslabs/aws-sdk-go/service/elb" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsProxyProtocolPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsProxyProtocolPolicyCreate, + Read: resourceAwsProxyProtocolPolicyRead, + Update: resourceAwsProxyProtocolPolicyUpdate, + Delete: resourceAwsProxyProtocolPolicyDelete, + + Schema: map[string]*schema.Schema{ + "load_balancer": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "instance_ports": &schema.Schema{ + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + }, + } +} + +func resourceAwsProxyProtocolPolicyCreate(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + elbname := aws.String(d.Get("load_balancer").(string)) + + input := &elb.CreateLoadBalancerPolicyInput{ + LoadBalancerName: elbname, + PolicyAttributes: []*elb.PolicyAttribute{ + &elb.PolicyAttribute{ + AttributeName: aws.String("ProxyProtocol"), + AttributeValue: aws.String("True"), + }, + }, + PolicyName: aws.String("TFEnableProxyProtocol"), + PolicyTypeName: aws.String("ProxyProtocolPolicyType"), + } + + // Create a policy + log.Printf("[DEBUG] ELB create a policy %s from policy type %s", + *input.PolicyName, *input.PolicyTypeName) + + if _, err := elbconn.CreateLoadBalancerPolicy(input); err != nil { + return fmt.Errorf("Error creating a policy %s: %s", + *input.PolicyName, err) + } + + // Assign the policy name for use later + d.Partial(true) + d.SetId(fmt.Sprintf("%s:%s", *elbname, *input.PolicyName)) + d.SetPartial("load_balancer") + log.Printf("[INFO] ELB PolicyName: %s", *input.PolicyName) + + return resourceAwsProxyProtocolPolicyUpdate(d, meta) +} + +func resourceAwsProxyProtocolPolicyRead(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + elbname := aws.String(d.Get("load_balancer").(string)) + + // Retrieve the current ELB policies for updating the state + req := &elb.DescribeLoadBalancersInput{ + LoadBalancerNames: []*string{elbname}, + } + resp, err := elbconn.DescribeLoadBalancers(req) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { + // The ELB is gone now, so just remove it from the state + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving ELB attributes: %s", err) + } + + backends := flattenBackendPolicies(resp.LoadBalancerDescriptions[0].BackendServerDescriptions) + + ports := []*string{} + for ip := range backends { + ipstr := strconv.Itoa(int(ip)) + ports = append(ports, &ipstr) + } + d.Set("instance_ports", ports) + d.Set("load_balancer", *elbname) + return nil +} + +func resourceAwsProxyProtocolPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + elbname := aws.String(d.Get("load_balancer").(string)) + + // Retrieve the current ELB policies for updating the state + req := &elb.DescribeLoadBalancersInput{ + LoadBalancerNames: []*string{elbname}, + } + resp, err := elbconn.DescribeLoadBalancers(req) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { + // The ELB is gone now, so just remove it from the state + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving ELB attributes: %s", err) + } + + backends := flattenBackendPolicies(resp.LoadBalancerDescriptions[0].BackendServerDescriptions) + _, policyName := resourceAwsProxyProtocolPolicyParseId(d.Id()) + + d.Partial(true) + if d.HasChange("instance_ports") { + o, n := d.GetChange("instance_ports") + os := o.(*schema.Set) + ns := n.(*schema.Set) + remove := os.Difference(ns).List() + add := ns.Difference(os).List() + + inputs := []*elb.SetLoadBalancerPoliciesForBackendServerInput{} + + i, err := resourceAwsProxyProtocolPolicyRemove(policyName, remove, backends) + if err != nil { + return err + } + inputs = append(inputs, i...) + + i, err = resourceAwsProxyProtocolPolicyAdd(policyName, add, backends) + if err != nil { + return err + } + inputs = append(inputs, i...) + + for _, input := range inputs { + input.LoadBalancerName = elbname + if _, err := elbconn.SetLoadBalancerPoliciesForBackendServer(input); err != nil { + return fmt.Errorf("Error setting policy for backend: %s", err) + } + } + + d.SetPartial("instance_ports") + } + + return resourceAwsProxyProtocolPolicyRead(d, meta) +} + +func resourceAwsProxyProtocolPolicyDelete(d *schema.ResourceData, meta interface{}) error { + elbconn := meta.(*AWSClient).elbconn + elbname := aws.String(d.Get("load_balancer").(string)) + + // Retrieve the current ELB policies for updating the state + req := &elb.DescribeLoadBalancersInput{ + LoadBalancerNames: []*string{elbname}, + } + resp, err := elbconn.DescribeLoadBalancers(req) + if err != nil { + if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { + // The ELB is gone now, so just remove it from the state + d.SetId("") + return nil + } + return fmt.Errorf("Error retrieving ELB attributes: %s", err) + } + + backends := flattenBackendPolicies(resp.LoadBalancerDescriptions[0].BackendServerDescriptions) + ports := d.Get("instance_ports").(*schema.Set).List() + _, policyName := resourceAwsProxyProtocolPolicyParseId(d.Id()) + + inputs, err := resourceAwsProxyProtocolPolicyRemove(policyName, ports, backends) + if err != nil { + return fmt.Errorf("Error detaching a policy from backend: %s", err) + } + for _, input := range inputs { + input.LoadBalancerName = elbname + if _, err := elbconn.SetLoadBalancerPoliciesForBackendServer(input); err != nil { + return fmt.Errorf("Error setting policy for backend: %s", err) + } + } + + pOpt := &elb.DeleteLoadBalancerPolicyInput{ + LoadBalancerName: elbname, + PolicyName: aws.String(policyName), + } + if _, err := elbconn.DeleteLoadBalancerPolicy(pOpt); err != nil { + return fmt.Errorf("Error removing a policy from load balancer: %s", err) + } + + return nil +} + +func resourceAwsProxyProtocolPolicyRemove(policyName string, ports []interface{}, backends map[int64][]string) ([]*elb.SetLoadBalancerPoliciesForBackendServerInput, error) { + inputs := make([]*elb.SetLoadBalancerPoliciesForBackendServerInput, 0, len(ports)) + for _, p := range ports { + ip, err := strconv.ParseInt(p.(string), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error detaching the policy: %s", err) + } + + newPolicies := []*string{} + curPolicies, found := backends[ip] + if !found { + // No policy for this instance port found, just skip it. + continue + } + + for _, policy := range curPolicies { + if policy == policyName { + // remove the policy + continue + } + newPolicies = append(newPolicies, &policy) + } + + inputs = append(inputs, &elb.SetLoadBalancerPoliciesForBackendServerInput{ + InstancePort: &ip, + PolicyNames: newPolicies, + }) + } + return inputs, nil +} + +func resourceAwsProxyProtocolPolicyAdd(policyName string, ports []interface{}, backends map[int64][]string) ([]*elb.SetLoadBalancerPoliciesForBackendServerInput, error) { + inputs := make([]*elb.SetLoadBalancerPoliciesForBackendServerInput, 0, len(ports)) + for _, p := range ports { + ip, err := strconv.ParseInt(p.(string), 10, 64) + if err != nil { + return nil, fmt.Errorf("Error attaching the policy: %s", err) + } + + newPolicies := []*string{} + curPolicies := backends[ip] + for _, p := range curPolicies { + if p == policyName { + // Just remove it for now. It will be back later. + continue + } else { + newPolicies = append(newPolicies, &p) + } + } + newPolicies = append(newPolicies, aws.String(policyName)) + + inputs = append(inputs, &elb.SetLoadBalancerPoliciesForBackendServerInput{ + InstancePort: &ip, + PolicyNames: newPolicies, + }) + } + return inputs, nil +} + +// resourceAwsProxyProtocolPolicyParseId takes an ID and parses it into +// it's constituent parts. You need two axes (LB name, policy name) +// to create or identify a proxy protocol policy in AWS's API. +func resourceAwsProxyProtocolPolicyParseId(id string) (string, string) { + parts := strings.SplitN(id, ":", 2) + return parts[0], parts[1] +} diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index 2f22d6e3a9..533780037f 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "sort" "strings" "github.com/awslabs/aws-sdk-go/aws" @@ -170,6 +171,18 @@ func expandInstanceString(list []interface{}) []*elb.Instance { return result } +// Flattens an array of Backend Descriptions into a a map of instance_port to policy names. +func flattenBackendPolicies(backends []*elb.BackendServerDescription) map[int64][]string { + policies := make(map[int64][]string) + for _, i := range backends { + for _, p := range i.PolicyNames { + policies[*i.InstancePort] = append(policies[*i.InstancePort], *p) + } + sort.Strings(policies[*i.InstancePort]) + } + return policies +} + // Flattens an array of Listeners into a []map[string]interface{} func flattenListeners(list []*elb.ListenerDescription) []map[string]interface{} { result := make([]map[string]interface{}, 0, len(list)) From 9df2bf68cb06ce48aecc2757dd868802d83a45ed Mon Sep 17 00:00:00 2001 From: TANABE Ken-ichi Date: Thu, 30 Apr 2015 01:31:23 +0900 Subject: [PATCH 2/5] providers/aws: use isLoadBalancerNotFound --- builtin/providers/aws/resource_aws_elb.go | 9 +++++++-- .../providers/aws/resource_aws_proxy_protocol_policy.go | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/builtin/providers/aws/resource_aws_elb.go b/builtin/providers/aws/resource_aws_elb.go index 419dc74f6a..f6b2e95ad9 100644 --- a/builtin/providers/aws/resource_aws_elb.go +++ b/builtin/providers/aws/resource_aws_elb.go @@ -243,7 +243,7 @@ func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error { describeResp, err := elbconn.DescribeLoadBalancers(describeElbOpts) if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { + if isLoadBalancerNotFound(err) { // The ELB is gone now, so just remove it from the state d.SetId("") return nil @@ -260,7 +260,7 @@ func resourceAwsElbRead(d *schema.ResourceData, meta interface{}) error { } describeAttrsResp, err := elbconn.DescribeLoadBalancerAttributes(describeAttrsOpts) if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { + if isLoadBalancerNotFound(err) { // The ELB is gone now, so just remove it from the state d.SetId("") return nil @@ -502,3 +502,8 @@ func resourceAwsElbListenerHash(v interface{}) int { return hashcode.String(buf.String()) } + +func isLoadBalancerNotFound(err error) bool { + elberr, ok := err.(aws.APIError) + return ok && elberr.Code == "LoadBalancerNotFound" +} diff --git a/builtin/providers/aws/resource_aws_proxy_protocol_policy.go b/builtin/providers/aws/resource_aws_proxy_protocol_policy.go index 2b988097d3..bdd5a8a5a4 100644 --- a/builtin/providers/aws/resource_aws_proxy_protocol_policy.go +++ b/builtin/providers/aws/resource_aws_proxy_protocol_policy.go @@ -81,7 +81,7 @@ func resourceAwsProxyProtocolPolicyRead(d *schema.ResourceData, meta interface{} } resp, err := elbconn.DescribeLoadBalancers(req) if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { + if isLoadBalancerNotFound(err) { // The ELB is gone now, so just remove it from the state d.SetId("") return nil @@ -111,7 +111,7 @@ func resourceAwsProxyProtocolPolicyUpdate(d *schema.ResourceData, meta interface } resp, err := elbconn.DescribeLoadBalancers(req) if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { + if isLoadBalancerNotFound(err) { // The ELB is gone now, so just remove it from the state d.SetId("") return nil @@ -167,7 +167,7 @@ func resourceAwsProxyProtocolPolicyDelete(d *schema.ResourceData, meta interface } resp, err := elbconn.DescribeLoadBalancers(req) if err != nil { - if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "LoadBalancerNotFound" { + if isLoadBalancerNotFound(err) { // The ELB is gone now, so just remove it from the state d.SetId("") return nil From 53a7da379c3949f3bb4926d9ac21f84117647502 Mon Sep 17 00:00:00 2001 From: TANABE Ken-ichi Date: Thu, 30 Apr 2015 16:32:02 +0900 Subject: [PATCH 3/5] provider/aws: Add acceptance tests for aws_proxy_protocol_policy --- ...resource_aws_proxy_protocol_policy_test.go | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_proxy_protocol_policy_test.go diff --git a/builtin/providers/aws/resource_aws_proxy_protocol_policy_test.go b/builtin/providers/aws/resource_aws_proxy_protocol_policy_test.go new file mode 100644 index 0000000000..d03cfd7eb5 --- /dev/null +++ b/builtin/providers/aws/resource_aws_proxy_protocol_policy_test.go @@ -0,0 +1,103 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSProxyProtocolPolicy(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckProxyProtocolPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccProxyProtocolPolicyConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_proxy_protocol_policy.smtp", "load_balancer", "test-lb"), + resource.TestCheckResourceAttr( + "aws_proxy_protocol_policy.smtp", "instance_ports.#", "1"), + resource.TestCheckResourceAttr( + "aws_proxy_protocol_policy.smtp", "instance_ports.4196041389", "25"), + ), + }, + resource.TestStep{ + Config: testAccProxyProtocolPolicyConfigUpdate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "aws_proxy_protocol_policy.smtp", "load_balancer", "test-lb"), + resource.TestCheckResourceAttr( + "aws_proxy_protocol_policy.smtp", "instance_ports.#", "2"), + resource.TestCheckResourceAttr( + "aws_proxy_protocol_policy.smtp", "instance_ports.4196041389", "25"), + resource.TestCheckResourceAttr( + "aws_proxy_protocol_policy.smtp", "instance_ports.1925441437", "587"), + ), + }, + }, + }) +} + +func testAccCheckProxyProtocolPolicyDestroy(s *terraform.State) error { + if len(s.RootModule().Resources) > 0 { + return fmt.Errorf("Expected all resources to be gone, but found: %#v", s.RootModule().Resources) + } + + return nil +} + +const testAccProxyProtocolPolicyConfig = ` +resource "aws_elb" "lb" { + name = "test-lb" + availability_zones = ["us-west-2a"] + + listener { + instance_port = 25 + instance_protocol = "tcp" + lb_port = 25 + lb_protocol = "tcp" + } + + listener { + instance_port = 587 + instance_protocol = "tcp" + lb_port = 587 + lb_protocol = "tcp" + } +} + +resource "aws_proxy_protocol_policy" "smtp" { + load_balancer = "${aws_elb.lb.name}" + instance_ports = ["25"] +} +` + +const testAccProxyProtocolPolicyConfigUpdate = ` +resource "aws_elb" "lb" { + name = "test-lb" + availability_zones = ["us-west-2a"] + + listener { + instance_port = 25 + instance_protocol = "tcp" + lb_port = 25 + lb_protocol = "tcp" + } + + listener { + instance_port = 587 + instance_protocol = "tcp" + lb_port = 587 + lb_protocol = "tcp" + } +} + +resource "aws_proxy_protocol_policy" "smtp" { + load_balancer = "${aws_elb.lb.name}" + instance_ports = ["25", "587"] +} +` From 638ca1e0df977ae3d9943918ef5842caa3ab02b5 Mon Sep 17 00:00:00 2001 From: TANABE Ken-ichi Date: Thu, 30 Apr 2015 22:19:57 +0900 Subject: [PATCH 4/5] providers/aws: Add docs for aws_proxy_protocol_policy --- .../aws/r/proxy_protocol_policy.html.markdown | 55 +++++++++++++++++++ website/source/layouts/aws.erb | 4 ++ 2 files changed, 59 insertions(+) create mode 100644 website/source/docs/providers/aws/r/proxy_protocol_policy.html.markdown diff --git a/website/source/docs/providers/aws/r/proxy_protocol_policy.html.markdown b/website/source/docs/providers/aws/r/proxy_protocol_policy.html.markdown new file mode 100644 index 0000000000..41701c4044 --- /dev/null +++ b/website/source/docs/providers/aws/r/proxy_protocol_policy.html.markdown @@ -0,0 +1,55 @@ +--- +layout: "aws" +page_title: "AWS: aws_proxy_protocol_policy" +sidebar_current: "docs-aws-proxy-protocol-policy" +description: |- + Provides an proxy protocol policy, which allows an ELB to carry a client connection information to a backend. +--- + +# aws\_proxy\_protocol\_policy + +Provides an proxy protocol policy, which allows an ELB to carry a client connection information to a backend. + +## Example Usage + +``` +resource "aws_elb" "lb" { + name = "test-lb" + availability_zones = ["us-east-1a"] + + listener { + instance_port = 25 + instance_protocol = "tcp" + lb_port = 25 + lb_protocol = "tcp" + } + + listener { + instance_port = 587 + instance_protocol = "tcp" + lb_port = 587 + lb_protocol = "tcp" + } +} + +resource "aws_proxy_protocol_policy" "smtp" { + load_balancer = "${aws_elb.lb.name}" + instance_ports = ["25", "587"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `load_balancer` - (Required) The load balancer to which the policy + should be attached. +* `instance_ports` - (Required) List of instance ports to which the policy + should be applied. This can be specified if the protocol is SSL or TCP. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the policy. +* `load_balancer` - The load balancer to which the policy is attached. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index eeaaaff851..3b115e5bdc 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -76,6 +76,10 @@ aws_key_pair + > + aws_proxy_protocol_policy + + > aws_route_table From 3b549a9e4f5d5c041d6b3c423254dcdf5b4a9604 Mon Sep 17 00:00:00 2001 From: TANABE Ken-ichi Date: Sat, 2 May 2015 07:34:22 +0900 Subject: [PATCH 5/5] Fix typo --- .../docs/providers/aws/r/proxy_protocol_policy.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/providers/aws/r/proxy_protocol_policy.html.markdown b/website/source/docs/providers/aws/r/proxy_protocol_policy.html.markdown index 41701c4044..8781f8ca6d 100644 --- a/website/source/docs/providers/aws/r/proxy_protocol_policy.html.markdown +++ b/website/source/docs/providers/aws/r/proxy_protocol_policy.html.markdown @@ -3,12 +3,12 @@ layout: "aws" page_title: "AWS: aws_proxy_protocol_policy" sidebar_current: "docs-aws-proxy-protocol-policy" description: |- - Provides an proxy protocol policy, which allows an ELB to carry a client connection information to a backend. + Provides a proxy protocol policy, which allows an ELB to carry a client connection information to a backend. --- # aws\_proxy\_protocol\_policy -Provides an proxy protocol policy, which allows an ELB to carry a client connection information to a backend. +Provides a proxy protocol policy, which allows an ELB to carry a client connection information to a backend. ## Example Usage