diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6d464ba..0252e6735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ FEATURES: -* Allow updating/resizing a Load Balancer through the `load_balancer_type` of `hcloud_load_balancer` resource +* Allow updating/resizing a Load Balancer through the + `load_balancer_type` of `hcloud_load_balancer` resource +* Add support for Load Balancer Label Selector and IP targets. ## 1.19.2 (July 28, 2020) @@ -21,7 +23,7 @@ CHANGED: BUG FIXES: * Enable and Disable `proxyprotocol` on a Load Balancer didn't work after creation -* Deleted all Load Balancer services when you changed the `listen_port` of one service +* Deleted all Load Balancer services when you changed the `listen_port` of one service * `hcloud_load_balancer_target` was not idempotent when you add a target that was already defined NOTES: diff --git a/go.mod b/go.mod index 784746905..f1e053c8a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/terraform-plugin-sdk v1.15.0 - github.com/hetznercloud/hcloud-go v1.19.0 + github.com/hetznercloud/hcloud-go v1.20.0 github.com/stretchr/testify v1.6.1 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 ) diff --git a/go.sum b/go.sum index 7fb04ad10..b3cc5b36f 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hetznercloud/hcloud-go v1.19.0 h1:8g28MQg8Eg97K7GASKUnaTZSNKo9CB73Xfxbt/NjvnU= -github.com/hetznercloud/hcloud-go v1.19.0/go.mod h1:EhElojlVU1biA5JgBaV8rRU1vE5+iYke402kXC9pooE= +github.com/hetznercloud/hcloud-go v1.20.0 h1:4EFRCoOlEUdg4qv4doVOKkGRpJhZlh/l6sUSJpConpw= +github.com/hetznercloud/hcloud-go v1.20.0/go.mod h1:EhElojlVU1biA5JgBaV8rRU1vE5+iYke402kXC9pooE= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= diff --git a/hcloud/resource_hcloud_load_balancer_target.go b/hcloud/resource_hcloud_load_balancer_target.go index 46d5f070d..e91f7ed4f 100644 --- a/hcloud/resource_hcloud_load_balancer_target.go +++ b/hcloud/resource_hcloud_load_balancer_target.go @@ -2,9 +2,11 @@ package hcloud import ( "context" + "crypto/sha256" "errors" "fmt" "log" + "net" "strings" "time" @@ -16,6 +18,7 @@ import ( var errLoadBalancerTargetNotFound = errors.New("load balancer target not found") func resourceLoadBalancerTarget() *schema.Resource { + targetProps := []string{"server_id", "label_selector", "ip"} return &schema.Resource{ Create: resourceLoadBalancerTargetCreate, Read: resourceLoadBalancerTargetRead, @@ -24,17 +27,33 @@ func resourceLoadBalancerTarget() *schema.Resource { Schema: map[string]*schema.Schema{ "type": { - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{"server"}, false), - Required: true, + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(hcloud.LoadBalancerTargetTypeServer), + string(hcloud.LoadBalancerTargetTypeLabelSelector), + string(hcloud.LoadBalancerTargetTypeIP), + }, false), + Required: true, }, "load_balancer_id": { Type: schema.TypeInt, Required: true, }, "server_id": { - Type: schema.TypeInt, - Optional: true, + Type: schema.TypeInt, + Optional: true, + ExactlyOneOf: targetProps, + }, + "label_selector": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: targetProps, + }, + "ip": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: targetProps, + ConflictsWith: []string{"use_private_ip"}, }, "use_private_ip": { Type: schema.TypeBool, @@ -46,18 +65,18 @@ func resourceLoadBalancerTarget() *schema.Resource { } func resourceLoadBalancerTargetCreate(d *schema.ResourceData, m interface{}) error { - var usePrivateIP bool + var ( + lb *hcloud.LoadBalancer + tgt hcloud.LoadBalancerTarget + action *hcloud.Action + err error + ) client := m.(*hcloud.Client) ctx := context.Background() - tgtType := d.Get("type").(string) - if tgtType != "server" { - return fmt.Errorf("unsupported target type: %s", tgtType) - } - lbID := d.Get("load_balancer_id").(int) - lb, _, err := client.LoadBalancer.GetByID(ctx, lbID) + lb, _, err = client.LoadBalancer.GetByID(ctx, lbID) if err != nil { return fmt.Errorf("get load balancer by id: %d: %v", lbID, err) } @@ -65,18 +84,49 @@ func resourceLoadBalancerTargetCreate(d *schema.ResourceData, m interface{}) err return fmt.Errorf("load balancer %d: not found", lbID) } + tgtType := hcloud.LoadBalancerTargetType(d.Get("type").(string)) + switch tgtType { + case hcloud.LoadBalancerTargetTypeServer: + action, tgt, err = resourceLoadBalancerCreateServerTarget(ctx, client, lb, d) + case hcloud.LoadBalancerTargetTypeLabelSelector: + action, tgt, err = resourceLoadBalancerCreateLabelSelectorTarget(ctx, client, lb, d) + case hcloud.LoadBalancerTargetTypeIP: + action, tgt, err = resourceLoadBalancerCreateIPTarget(ctx, client, lb, d) + default: + return fmt.Errorf("unsupported target type: %s", tgtType) + } + if err != nil { + return err + } + if action != nil { + if err := waitForLoadBalancerAction(ctx, client, action, lb); err != nil { + return fmt.Errorf("add load balancer target: %v", err) + } + } + setLoadBalancerTarget(d, lbID, tgt) + return nil +} + +func resourceLoadBalancerCreateServerTarget( + ctx context.Context, client *hcloud.Client, lb *hcloud.LoadBalancer, d *schema.ResourceData, +) (*hcloud.Action, hcloud.LoadBalancerTarget, error) { + var ( + usePrivateIP bool + tgt hcloud.LoadBalancerTarget + ) + sid, ok := d.GetOk("server_id") if !ok { - return fmt.Errorf("target type server: missing server_id") + return nil, tgt, fmt.Errorf("target type server: missing server_id") } serverID := sid.(int) server, _, err := client.Server.GetByID(ctx, serverID) if err != nil { - return fmt.Errorf("get server by id: %d: %v", serverID, err) + return nil, tgt, fmt.Errorf("get server by id: %d: %v", serverID, err) } if server == nil { - return fmt.Errorf("server %d: not found", serverID) + return nil, tgt, fmt.Errorf("server %d: not found", serverID) } opts := hcloud.LoadBalancerAddServerTargetOpts{ @@ -91,44 +141,104 @@ func resourceLoadBalancerTargetCreate(d *schema.ResourceData, m interface{}) err log.Printf("[INFO] Load balancer (%d) not (yet) attached to a network. Retrying in one second", lb.ID) time.Sleep(time.Second) - lb, _, err = client.LoadBalancer.GetByID(ctx, lbID) + lb, _, err = client.LoadBalancer.GetByID(ctx, lb.ID) if err != nil { - return fmt.Errorf("get load balancer by id: %d: %v", lbID, err) + return nil, tgt, fmt.Errorf("get load balancer by id: %d: %v", lb.ID, err) } if lb == nil { - return fmt.Errorf("load balancer %d: not found", lbID) + return nil, tgt, fmt.Errorf("load balancer %d: not found", lb.ID) } } - + tgt = hcloud.LoadBalancerTarget{ + Type: hcloud.LoadBalancerTargetTypeServer, + Server: &hcloud.LoadBalancerTargetServer{Server: server}, + UsePrivateIP: usePrivateIP, + } action, _, err := client.LoadBalancer.AddServerTarget(ctx, lb, opts) if err != nil { if hcloud.IsError(err, "target_already_defined") { // TODO: use const when hcloud go is released - setLoadBalancerTarget(d, lb.ID, hcloud.LoadBalancerTarget{ - Type: hcloud.LoadBalancerTargetTypeServer, - Server: &hcloud.LoadBalancerTargetServer{Server: server}, - UsePrivateIP: usePrivateIP, - }) - return nil + return nil, tgt, nil } - return fmt.Errorf("add server target: %v", err) + return nil, tgt, fmt.Errorf("add server target: %v", err) } - if err := waitForLoadBalancerAction(ctx, client, action, lb); err != nil { - return fmt.Errorf("add server target: %v", err) + + return action, tgt, nil +} + +func resourceLoadBalancerCreateLabelSelectorTarget( + ctx context.Context, client *hcloud.Client, lb *hcloud.LoadBalancer, d *schema.ResourceData, +) (*hcloud.Action, hcloud.LoadBalancerTarget, error) { + var ( + opts hcloud.LoadBalancerAddLabelSelectorTargetOpts + tgt hcloud.LoadBalancerTarget + ) + + if v, ok := d.GetOk("label_selector"); ok { + opts.Selector = v.(string) } - setLoadBalancerTarget(d, lb.ID, hcloud.LoadBalancerTarget{ - Type: hcloud.LoadBalancerTargetTypeServer, - Server: &hcloud.LoadBalancerTargetServer{Server: server}, - UsePrivateIP: usePrivateIP, - }) - return nil + if opts.Selector == "" { + return nil, tgt, fmt.Errorf("label_selector is missing") + } + + if v, ok := d.GetOk("use_private_ip"); ok { + opts.UsePrivateIP = hcloud.Bool(v.(bool)) + } + + tgt = hcloud.LoadBalancerTarget{ + Type: hcloud.LoadBalancerTargetTypeLabelSelector, + LabelSelector: &hcloud.LoadBalancerTargetLabelSelector{ + Selector: opts.Selector, + }, + UsePrivateIP: opts.UsePrivateIP != nil && *opts.UsePrivateIP, + } + + action, _, err := client.LoadBalancer.AddLabelSelectorTarget(ctx, lb, opts) + if err != nil && hcloud.IsError(err, "target_already_defined") { + return nil, tgt, nil + } + if err != nil { + return nil, tgt, fmt.Errorf("add label selector target: %v", err) + } + return action, tgt, nil +} + +func resourceLoadBalancerCreateIPTarget( + ctx context.Context, client *hcloud.Client, lb *hcloud.LoadBalancer, d *schema.ResourceData, +) (*hcloud.Action, hcloud.LoadBalancerTarget, error) { + var ( + opts hcloud.LoadBalancerAddIPTargetOpts + tgt hcloud.LoadBalancerTarget + ) + + if v, ok := d.GetOk("ip"); ok { + opts.IP = net.ParseIP(v.(string)) + } + if opts.IP == nil { + return nil, tgt, fmt.Errorf("ip is missing or invalid") + } + + tgt = hcloud.LoadBalancerTarget{ + Type: hcloud.LoadBalancerTargetTypeIP, + IP: &hcloud.LoadBalancerTargetIP{IP: opts.IP.String()}, + } + + action, _, err := client.LoadBalancer.AddIPTarget(ctx, lb, opts) + if err != nil && hcloud.IsError(err, "target_already_defined") { + return nil, tgt, nil + } + if err != nil { + return nil, tgt, fmt.Errorf("add label selector target: %v", err) + } + return action, tgt, nil } func resourceLoadBalancerTargetRead(d *schema.ResourceData, m interface{}) error { client := m.(*hcloud.Client) ctx := context.Background() lbID := d.Get("load_balancer_id").(int) + tgtType := hcloud.LoadBalancerTargetType(d.Get("type").(string)) - _, tgt, err := findLoadBalancerTarget(ctx, client, lbID, d) + _, tgt, err := findLoadBalancerTarget(ctx, client, lbID, tgtType, d) if err != nil { return err } @@ -141,54 +251,60 @@ func resourceLoadBalancerTargetUpdate(d *schema.ResourceData, m interface{}) err client := m.(*hcloud.Client) ctx := context.Background() lbID := d.Get("load_balancer_id").(int) + tgtType := hcloud.LoadBalancerTargetType(d.Get("type").(string)) - lb, tgt, err := findLoadBalancerTarget(ctx, client, lbID, d) + lb, tgt, err := findLoadBalancerTarget(ctx, client, lbID, tgtType, d) if errors.Is(err, errLoadBalancerTargetNotFound) { return resourceLoadBalancerTargetCreate(d, m) } if err != nil { return err } - - action, _, err := client.LoadBalancer.RemoveServerTarget(ctx, lb, tgt.Server.Server) - if hcloud.IsError(err, hcloud.ErrorCodeConflict) || hcloud.IsError(err, hcloud.ErrorCodeLocked) { - // Retry after a short delay - time.Sleep(time.Second) - action, _, err = client.LoadBalancer.RemoveServerTarget(ctx, lb, tgt.Server.Server) - } - if hcloud.IsError(err, hcloud.ErrorCodeNotFound) { - return resourceLoadBalancerTargetCreate(d, m) - } - if err != nil { - return fmt.Errorf("remove existing target: %v", err) - } - if err := waitForLoadBalancerAction(ctx, client, action, lb); err != nil { - return fmt.Errorf("remove existing target: %v", err) + if err := removeLoadBalancerTarget(ctx, client, lb, tgt); err != nil { + return err } - return resourceLoadBalancerTargetCreate(d, m) } func resourceLoadBalancerTargetDelete(d *schema.ResourceData, m interface{}) error { client := m.(*hcloud.Client) ctx := context.Background() + tgtType := hcloud.LoadBalancerTargetType(d.Get("type").(string)) lbID := d.Get("load_balancer_id").(int) - lb, tgt, err := findLoadBalancerTarget(ctx, client, lbID, d) + lb, tgt, err := findLoadBalancerTarget(ctx, client, lbID, tgtType, d) if errors.Is(err, errLoadBalancerTargetNotFound) { return nil } if err != nil { return err } + return removeLoadBalancerTarget(ctx, client, lb, tgt) +} - action, _, err := client.LoadBalancer.RemoveServerTarget(ctx, lb, tgt.Server.Server) - if hcloud.IsError(err, hcloud.ErrorCodeConflict) || hcloud.IsError(err, hcloud.ErrorCodeLocked) { - // Retry after a short delay - time.Sleep(time.Second) - action, _, err = client.LoadBalancer.RemoveServerTarget(ctx, lb, tgt.Server.Server) - } - if err != nil { +func removeLoadBalancerTarget(ctx context.Context, client *hcloud.Client, lb *hcloud.LoadBalancer, tgt hcloud.LoadBalancerTarget) error { + for i := 0; i < 3; i++ { + var ( + action *hcloud.Action + err error + ) + + switch tgt.Type { + case hcloud.LoadBalancerTargetTypeServer: + action, _, err = client.LoadBalancer.RemoveServerTarget(ctx, lb, tgt.Server.Server) + case hcloud.LoadBalancerTargetTypeLabelSelector: + action, _, err = client.LoadBalancer.RemoveLabelSelectorTarget(ctx, lb, tgt.LabelSelector.Selector) + case hcloud.LoadBalancerTargetTypeIP: + action, _, err = client.LoadBalancer.RemoveIPTarget(ctx, lb, net.ParseIP(tgt.IP.IP)) + default: + return fmt.Errorf("unsupported target type: %s", tgt.Type) + } + + if hcloud.IsError(err, hcloud.ErrorCodeConflict) || hcloud.IsError(err, hcloud.ErrorCodeLocked) { + // Retry after a short delay + time.Sleep(time.Duration(i+1) * time.Second) + continue + } if hcErr, ok := err.(hcloud.Error); ok { if hcErr.Code == "load_balancer_target_not_found" || strings.Contains(hcErr.Message, "target not found") { // Target has been deleted already (e.g. by deleting the @@ -196,19 +312,25 @@ func resourceLoadBalancerTargetDelete(d *schema.ResourceData, m interface{}) err return nil } } - return fmt.Errorf("remove server target: %v", err) - } - if err := waitForLoadBalancerAction(ctx, client, action, lb); err != nil { - return fmt.Errorf("remove server target: wait for action: %v", err) + if err != nil { + return fmt.Errorf("remove server target: %v", err) + } + if err := waitForLoadBalancerAction(ctx, client, action, lb); err != nil { + return fmt.Errorf("remove server target: wait for action: %v", err) + } + return nil } - return nil } func findLoadBalancerTarget( - ctx context.Context, client *hcloud.Client, lbID int, d *schema.ResourceData, + ctx context.Context, client *hcloud.Client, lbID int, tgtType hcloud.LoadBalancerTargetType, d *schema.ResourceData, ) (*hcloud.LoadBalancer, hcloud.LoadBalancerTarget, error) { - var serverID int + var ( + serverID int + labelSelector string + ip string + ) lb, _, err := client.LoadBalancer.GetByID(ctx, lbID) if err != nil { @@ -217,15 +339,35 @@ func findLoadBalancerTarget( if lb == nil { return nil, hcloud.LoadBalancerTarget{}, fmt.Errorf("load balancer %d: not found", lbID) } - if sid, ok := d.GetOk("server_id"); ok { - serverID = sid.(int) + if v, ok := d.GetOk("server_id"); ok { + serverID = v.(int) + } + if v, ok := d.GetOk("label_selector"); ok { + labelSelector = v.(string) + } + if v, ok := d.GetOk("ip"); ok { + ip = v.(string) } for _, tgt := range lb.Targets { - if tgt.Type == hcloud.LoadBalancerTargetTypeServer && tgt.Server.Server.ID == serverID { - return lb, tgt, nil + switch tgt.Type { + case hcloud.LoadBalancerTargetTypeServer: + if tgt.Server.Server.ID == serverID { + return lb, tgt, nil + } + case hcloud.LoadBalancerTargetTypeLabelSelector: + if tgt.LabelSelector.Selector == labelSelector { + return lb, tgt, nil + } + case hcloud.LoadBalancerTargetTypeIP: + if tgt.IP.IP == ip { + return lb, tgt, nil + } + default: + return nil, hcloud.LoadBalancerTarget{}, fmt.Errorf("unsupported target type: %s", tgtType) } } + return nil, hcloud.LoadBalancerTarget{}, errLoadBalancerTargetNotFound } @@ -234,14 +376,32 @@ func setLoadBalancerTarget(d *schema.ResourceData, lbID int, tgt hcloud.LoadBala d.Set("load_balancer_id", lbID) d.Set("use_private_ip", tgt.UsePrivateIP) - if tgt.Type == hcloud.LoadBalancerTargetTypeServer { + switch tgt.Type { + case hcloud.LoadBalancerTargetTypeServer: d.Set("server_id", tgt.Server.Server.ID) - tgtID := generateLoadBalancerServerTargetID(tgt.Server.Server, lbID) d.SetId(tgtID) + case hcloud.LoadBalancerTargetTypeLabelSelector: + d.Set("label_selector", tgt.LabelSelector) + tgtID := generateLoadBalancerLabelSelectorTargetID(tgt.LabelSelector.Selector, lbID) + d.SetId(tgtID) + case hcloud.LoadBalancerTargetTypeIP: + d.Set("ip", tgt.IP.IP) + tgtID := generateLoadBalancerIPTargetID(tgt.IP.IP, lbID) + d.SetId(tgtID) } } func generateLoadBalancerServerTargetID(srv *hcloud.Server, lbID int) string { return fmt.Sprintf("lb-srv-tgt-%d-%d", srv.ID, lbID) } + +func generateLoadBalancerLabelSelectorTargetID(selector string, lbID int) string { + h := sha256.Sum256([]byte(selector)) + return fmt.Sprintf("lb-label-selector-tgt-%x-%d", h, lbID) +} + +func generateLoadBalancerIPTargetID(ip string, lbID int) string { + h := sha256.Sum256([]byte(ip)) + return fmt.Sprintf("lb-ip-tgt-%x-%d", h, lbID) +} diff --git a/internal/loadbalancer/resource_target_test.go b/internal/loadbalancer/resource_target_test.go index 0cefc52aa..161ecf57d 100644 --- a/internal/loadbalancer/resource_target_test.go +++ b/internal/loadbalancer/resource_target_test.go @@ -14,7 +14,7 @@ import ( "github.com/terraform-providers/terraform-provider-hcloud/internal/testtemplate" ) -func TestAccHcloudLoadBalancerTarget(t *testing.T) { +func TestAccHcloudLoadBalancerTarget_ServerTarget(t *testing.T) { var ( lb hcloud.LoadBalancer srv hcloud.Server @@ -67,7 +67,7 @@ func TestAccHcloudLoadBalancerTarget(t *testing.T) { }) } -func TestAccHcloudLoadBalancerTarget_UsePrivateIP(t *testing.T) { +func TestAccHcloudLoadBalancerTarget_ServerTarget_UsePrivateIP(t *testing.T) { var ( lb hcloud.LoadBalancer srv hcloud.Server @@ -139,6 +139,177 @@ func TestAccHcloudLoadBalancerTarget_UsePrivateIP(t *testing.T) { }) } +func TestAccHcloudLoadBalancerTarget_LabelSelectorTarget(t *testing.T) { + var ( + lb hcloud.LoadBalancer + srv hcloud.Server + ) + + tmplMan := testtemplate.Manager{} + selector := fmt.Sprintf("tf-test=tf-test-%d", tmplMan.RandInt) + resource.Test(t, resource.TestCase{ + PreCheck: testsupport.AccTestPreCheck(t), + Providers: testsupport.AccTestProviders(), + CheckDestroy: testsupport.CheckResourcesDestroyed(loadbalancer.ResourceType, loadbalancer.ByID(t, nil)), + Steps: []resource.TestStep{ + { + Config: tmplMan.Render(t, + "testdata/r/hcloud_server", &server.RData{ + Name: "lb-server-target", + Type: "cx11", + Image: "ubuntu-20.04", + Labels: map[string]string{ + "tf-test": fmt.Sprintf("tf-test-%d", tmplMan.RandInt), + }, + }, + "testdata/r/hcloud_load_balancer", &loadbalancer.RData{ + Name: "target-test-lb", + Type: "lb11", + NetworkZone: "eu-central", + }, + "testdata/r/hcloud_load_balancer_target", &loadbalancer.RDataTarget{ + Name: "lb-test-target", + Type: "label_selector", + LoadBalancerID: "hcloud_load_balancer.target-test-lb.id", + LabelSelector: selector, + }, + ), + Check: resource.ComposeTestCheckFunc( + testsupport.CheckResourceExists( + loadbalancer.ResourceType+".target-test-lb", loadbalancer.ByID(t, &lb)), + testsupport.CheckResourceExists( + server.ResourceType+".lb-server-target", server.ByID(t, &srv)), + resource.TestCheckResourceAttr( + loadbalancer.TargetResourceType+".lb-test-target", "type", "label_selector"), + resource.TestCheckResourceAttr( + loadbalancer.TargetResourceType+".lb-test-target", "label_selector", selector), + testsupport.LiftTCF(hasLabelSelectorTarget(&lb, selector)), + ), + }, + }, + }) +} + +func TestAccHcloudLoadBalancerTarget_LabelSelectorTarget_UsePrivateIP(t *testing.T) { + var ( + lb hcloud.LoadBalancer + srv hcloud.Server + ) + + tmplMan := testtemplate.Manager{} + selector := fmt.Sprintf("tf-test=tf-test-%d", tmplMan.RandInt) + resource.Test(t, resource.TestCase{ + PreCheck: testsupport.AccTestPreCheck(t), + Providers: testsupport.AccTestProviders(), + CheckDestroy: testsupport.CheckResourcesDestroyed(loadbalancer.ResourceType, loadbalancer.ByID(t, nil)), + Steps: []resource.TestStep{ + { + Config: tmplMan.Render(t, + "testdata/r/hcloud_network", &network.RData{ + Name: "lb-target-test-network", + IPRange: "10.0.0.0/16", + }, + "testdata/r/hcloud_network_subnet", &network.RDataSubnet{ + Name: "lb-target-test-subnet", + NetworkID: "hcloud_network.lb-target-test-network.id", + Type: "cloud", + NetworkZone: "eu-central", + IPRange: "10.0.1.0/24", + }, + "testdata/r/hcloud_server", &server.RData{ + Name: "lb-server-target", + Type: "cx11", + Image: "ubuntu-20.04", + Labels: map[string]string{ + "tf-test": fmt.Sprintf("tf-test-%d", tmplMan.RandInt), + }, + }, + "testdata/r/hcloud_server_network", &server.RDataNetwork{ + Name: "lb-server-network", + ServerID: "hcloud_server.lb-server-target.id", + NetworkID: "hcloud_network.lb-target-test-network.id", + }, + "testdata/r/hcloud_load_balancer", &loadbalancer.RData{ + Name: "target-test-lb", + Type: "lb11", + NetworkZone: "eu-central", + }, + "testdata/r/hcloud_load_balancer_network", &loadbalancer.RDataNetwork{ + Name: "target-test-lb-network", + LoadBalancerID: "hcloud_load_balancer.target-test-lb.id", + NetworkID: "hcloud_network.lb-target-test-network.id", + EnablePublicInterface: true, + }, + "testdata/r/hcloud_load_balancer_target", &loadbalancer.RDataTarget{ + Name: "lb-test-target", + Type: "label_selector", + LoadBalancerID: "hcloud_load_balancer.target-test-lb.id", + LabelSelector: selector, + UsePrivateIP: true, + DependsOn: []string{ + "hcloud_server_network.lb-server-network", + "hcloud_load_balancer_network.target-test-lb-network", + }, + }, + ), + Check: resource.ComposeTestCheckFunc( + testsupport.CheckResourceExists( + loadbalancer.ResourceType+".target-test-lb", loadbalancer.ByID(t, &lb)), + testsupport.CheckResourceExists( + server.ResourceType+".lb-server-target", server.ByID(t, &srv)), + resource.TestCheckResourceAttr( + loadbalancer.TargetResourceType+".lb-test-target", "type", "label_selector"), + resource.TestCheckResourceAttr( + loadbalancer.TargetResourceType+".lb-test-target", "label_selector", selector), + resource.TestCheckResourceAttr( + loadbalancer.TargetResourceType+".lb-test-target", "use_private_ip", "true"), + testsupport.LiftTCF(hasLabelSelectorTarget(&lb, selector)), + ), + }, + }, + }) +} + +func TestAccHcloudLoadBalancerTarget_IPTarget(t *testing.T) { + var ( + lb hcloud.LoadBalancer + ) + + ip := "213.239.214.25" + tmplMan := testtemplate.Manager{} + resource.Test(t, resource.TestCase{ + PreCheck: testsupport.AccTestPreCheck(t), + Providers: testsupport.AccTestProviders(), + CheckDestroy: testsupport.CheckResourcesDestroyed(loadbalancer.ResourceType, loadbalancer.ByID(t, nil)), + Steps: []resource.TestStep{ + { + Config: tmplMan.Render(t, + "testdata/r/hcloud_load_balancer", &loadbalancer.RData{ + Name: "target-test-lb", + Type: "lb11", + NetworkZone: "eu-central", + }, + "testdata/r/hcloud_load_balancer_target", &loadbalancer.RDataTarget{ + Name: "lb-test-target", + LoadBalancerID: "hcloud_load_balancer.target-test-lb.id", + Type: "ip", + IP: ip, + }, + ), + Check: resource.ComposeTestCheckFunc( + testsupport.CheckResourceExists( + loadbalancer.ResourceType+".target-test-lb", loadbalancer.ByID(t, &lb)), + resource.TestCheckResourceAttr( + loadbalancer.TargetResourceType+".lb-test-target", "type", "ip"), + resource.TestCheckResourceAttr( + loadbalancer.TargetResourceType+".lb-test-target", "ip", ip), + testsupport.LiftTCF(hasIPTarget(&lb, ip)), + ), + }, + }, + }) +} + func hasServerTarget(lb *hcloud.LoadBalancer, srv *hcloud.Server) func() error { return func() error { for _, tgt := range lb.Targets { @@ -149,3 +320,25 @@ func hasServerTarget(lb *hcloud.LoadBalancer, srv *hcloud.Server) func() error { return fmt.Errorf("load balancer %d: no target for server: %d", lb.ID, srv.ID) } } + +func hasLabelSelectorTarget(lb *hcloud.LoadBalancer, selector string) func() error { + return func() error { + for _, tgt := range lb.Targets { + if tgt.Type == hcloud.LoadBalancerTargetTypeLabelSelector && tgt.LabelSelector.Selector == selector { + return nil + } + } + return fmt.Errorf("load balancer %d: no label selector: %s", lb.ID, selector) + } +} + +func hasIPTarget(lb *hcloud.LoadBalancer, ip string) func() error { + return func() error { + for _, tgt := range lb.Targets { + if tgt.Type == hcloud.LoadBalancerTargetTypeIP && tgt.IP.IP == ip { + return nil + } + } + return fmt.Errorf("load balancer %d: no ip target: %s", lb.ID, ip) + } +} diff --git a/internal/loadbalancer/testing.go b/internal/loadbalancer/testing.go index a235bdde7..1a4a7b655 100644 --- a/internal/loadbalancer/testing.go +++ b/internal/loadbalancer/testing.go @@ -171,6 +171,8 @@ type RDataTarget struct { Type string LoadBalancerID string ServerID string + LabelSelector string + IP string UsePrivateIP bool DependsOn []string } diff --git a/internal/testdata/r/hcloud_load_balancer_target.tf.tmpl b/internal/testdata/r/hcloud_load_balancer_target.tf.tmpl index d756e8129..5814a27e6 100644 --- a/internal/testdata/r/hcloud_load_balancer_target.tf.tmpl +++ b/internal/testdata/r/hcloud_load_balancer_target.tf.tmpl @@ -4,7 +4,15 @@ resource "hcloud_load_balancer_target" "{{ .Name }}" { {{/* Required properties */ -}} type = "{{ .Type }}" load_balancer_id = {{ .LoadBalancerID }} + {{- if .ServerID }} server_id = {{ .ServerID }} + {{ end }} + {{- if .LabelSelector }} + label_selector = "{{ .LabelSelector }}" + {{ end }} + {{- if .IP }} + ip = "{{ .IP }}" + {{ end }} {{- /* Optional properties */}} {{- if .UsePrivateIP }} diff --git a/website/docs/r/load_balancer_target.html.md b/website/docs/r/load_balancer_target.html.md index 068a9666a..165825bad 100644 --- a/website/docs/r/load_balancer_target.html.md +++ b/website/docs/r/load_balancer_target.html.md @@ -34,18 +34,27 @@ resource "hcloud_load_balancer_target" "load_balancer_target" { ## Argument Reference -- `type` - (Required, string) Type of the target. `server` +- `type` - (Required, string) Type of the target. Possible values + `server`, `label_selector`, `ip`. - `load_balancer_id` - (Required, int) ID of the Load Balancer to which the target gets attached. - `server_id` - (Optional, int) ID of the server which should be a target for this Load Balancer. Required if `type` is `server` +- `label_selector` - (Optional, string) Label Selector selecting targets + for this Load Balancer. Required if `type` is `label_selector`. +- `ip` - (Optional, string) IP address for an IP Target. Required if + `type` is `ip`. - `use_private_ip` - (Optional, bool) use the private IP to connect to - Load Balancer targets. + Load Balancer targets. Only allowed if type is `server` or + `label_selector`. ## Attributes Reference - `type` - (string) Type of the target. `server` - `server_id` - (int) ID of the server which should be a target for this Load Balancer. +- `label_selector` - (string) Label Selector selecting targets for this + Load Balancer. +- `ip` - (string) IP address of an IP Target. - `use_private_ip` - (bool) use the private IP to connect to Load Balancer targets.