| // |
| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| // |
| |
| package cloudstack |
| |
| import ( |
| "fmt" |
| "log" |
| "strings" |
| |
| "github.com/apache/cloudstack-go/v2/cloudstack" |
| "github.com/hashicorp/terraform/helper/schema" |
| ) |
| |
| func resourceCloudStackLoadBalancerRule() *schema.Resource { |
| return &schema.Resource{ |
| Create: resourceCloudStackLoadBalancerRuleCreate, |
| Read: resourceCloudStackLoadBalancerRuleRead, |
| Update: resourceCloudStackLoadBalancerRuleUpdate, |
| Delete: resourceCloudStackLoadBalancerRuleDelete, |
| |
| Schema: map[string]*schema.Schema{ |
| "name": { |
| Type: schema.TypeString, |
| Required: true, |
| }, |
| |
| "description": { |
| Type: schema.TypeString, |
| Optional: true, |
| Computed: true, |
| }, |
| |
| "ip_address_id": { |
| Type: schema.TypeString, |
| Required: true, |
| ForceNew: true, |
| }, |
| |
| "network_id": { |
| Type: schema.TypeString, |
| Optional: true, |
| ForceNew: true, |
| }, |
| |
| "algorithm": { |
| Type: schema.TypeString, |
| Required: true, |
| }, |
| |
| "certificate_id": { |
| Type: schema.TypeString, |
| Optional: true, |
| }, |
| |
| "private_port": { |
| Type: schema.TypeInt, |
| Required: true, |
| ForceNew: true, |
| }, |
| |
| "public_port": { |
| Type: schema.TypeInt, |
| Required: true, |
| ForceNew: true, |
| }, |
| |
| "protocol": { |
| Type: schema.TypeString, |
| Optional: true, |
| Computed: true, |
| ForceNew: true, |
| }, |
| |
| "member_ids": { |
| Type: schema.TypeSet, |
| Required: true, |
| ForceNew: false, |
| Elem: &schema.Schema{Type: schema.TypeString}, |
| Set: schema.HashString, |
| }, |
| |
| "project": { |
| Type: schema.TypeString, |
| Optional: true, |
| Computed: true, |
| ForceNew: true, |
| }, |
| }, |
| } |
| } |
| |
| func resourceCloudStackLoadBalancerRuleCreate(d *schema.ResourceData, meta interface{}) error { |
| cs := meta.(*cloudstack.CloudStackClient) |
| |
| // Make sure all required parameters are there |
| if err := verifyLoadBalancerRule(d); err != nil { |
| return err |
| } |
| |
| d.Partial(true) |
| |
| // Create a new parameter struct |
| p := cs.LoadBalancer.NewCreateLoadBalancerRuleParams( |
| d.Get("algorithm").(string), |
| d.Get("name").(string), |
| d.Get("private_port").(int), |
| d.Get("public_port").(int), |
| ) |
| |
| // Don't autocreate a firewall rule, use a resource if needed |
| p.SetOpenfirewall(false) |
| |
| // Set the description |
| if description, ok := d.GetOk("description"); ok { |
| p.SetDescription(description.(string)) |
| } else { |
| p.SetDescription(d.Get("name").(string)) |
| } |
| |
| if networkid, ok := d.GetOk("network_id"); ok { |
| // Set the network id |
| p.SetNetworkid(networkid.(string)) |
| } |
| |
| // Set the protocol |
| if protocol, ok := d.GetOk("protocol"); ok { |
| p.SetProtocol(protocol.(string)) |
| } |
| |
| // Set the ipaddress id |
| p.SetPublicipid(d.Get("ip_address_id").(string)) |
| |
| // Create the load balancer rule |
| r, err := cs.LoadBalancer.CreateLoadBalancerRule(p) |
| if err != nil { |
| return err |
| } |
| |
| // Set the load balancer rule ID and set partials |
| d.SetId(r.Id) |
| d.SetPartial("name") |
| d.SetPartial("description") |
| d.SetPartial("ip_address_id") |
| d.SetPartial("network_id") |
| d.SetPartial("algorithm") |
| d.SetPartial("private_port") |
| d.SetPartial("public_port") |
| d.SetPartial("protocol") |
| |
| if certificateID, ok := d.GetOk("certificate_id"); ok { |
| // Create a new parameter struct |
| cp := cs.LoadBalancer.NewAssignCertToLoadBalancerParams(certificateID.(string), r.Id) |
| if _, err := cs.LoadBalancer.AssignCertToLoadBalancer(cp); err != nil { |
| return err |
| } |
| } |
| d.SetPartial("certificate_id") |
| |
| // Create a new parameter struct |
| mp := cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(r.Id) |
| |
| var mbs []string |
| for _, id := range d.Get("member_ids").(*schema.Set).List() { |
| mbs = append(mbs, id.(string)) |
| } |
| |
| mp.SetVirtualmachineids(mbs) |
| |
| _, err = cs.LoadBalancer.AssignToLoadBalancerRule(mp) |
| if err != nil { |
| return err |
| } |
| |
| d.SetPartial("member_ids") |
| d.Partial(false) |
| |
| return resourceCloudStackLoadBalancerRuleRead(d, meta) |
| } |
| |
| func resourceCloudStackLoadBalancerRuleRead(d *schema.ResourceData, meta interface{}) error { |
| cs := meta.(*cloudstack.CloudStackClient) |
| |
| // Get the load balancer details |
| lb, count, err := cs.LoadBalancer.GetLoadBalancerRuleByID( |
| d.Id(), |
| cloudstack.WithProject(d.Get("project").(string)), |
| ) |
| if err != nil { |
| if count == 0 { |
| log.Printf("[DEBUG] Load balancer rule %s does no longer exist", d.Get("name").(string)) |
| d.SetId("") |
| return nil |
| } |
| |
| return err |
| } |
| |
| d.Set("name", lb.Name) |
| d.Set("ip_address_id", lb.Publicipid) |
| d.Set("algorithm", lb.Algorithm) |
| d.Set("public_port", lb.Publicport) |
| d.Set("private_port", lb.Privateport) |
| d.Set("protocol", lb.Protocol) |
| |
| // Only set network if user specified it to avoid spurious diffs |
| if _, ok := d.GetOk("network_id"); ok { |
| d.Set("network_id", lb.Networkid) |
| } |
| |
| setValueOrID(d, "project", lb.Project, lb.Projectid) |
| |
| p := cs.LoadBalancer.NewListLoadBalancerRuleInstancesParams(d.Id()) |
| l, err := cs.LoadBalancer.ListLoadBalancerRuleInstances(p) |
| if err != nil { |
| return err |
| } |
| |
| var mbs []string |
| for _, i := range l.LoadBalancerRuleInstances { |
| mbs = append(mbs, i.Id) |
| } |
| d.Set("member_ids", mbs) |
| |
| return nil |
| } |
| |
| func resourceCloudStackLoadBalancerRuleUpdate(d *schema.ResourceData, meta interface{}) error { |
| cs := meta.(*cloudstack.CloudStackClient) |
| |
| // Make sure all required parameters are there |
| if err := verifyLoadBalancerRule(d); err != nil { |
| return err |
| } |
| |
| if d.HasChange("name") || d.HasChange("description") || d.HasChange("algorithm") { |
| name := d.Get("name").(string) |
| |
| // Create new parameter struct |
| p := cs.LoadBalancer.NewUpdateLoadBalancerRuleParams(d.Id()) |
| |
| if d.HasChange("name") { |
| log.Printf("[DEBUG] Name has changed for load balancer rule %s, starting update", name) |
| |
| p.SetName(name) |
| } |
| |
| if d.HasChange("description") { |
| log.Printf( |
| "[DEBUG] Description has changed for load balancer rule %s, starting update", name) |
| |
| p.SetDescription(d.Get("description").(string)) |
| } |
| |
| if d.HasChange("algorithm") { |
| algorithm := d.Get("algorithm").(string) |
| |
| log.Printf( |
| "[DEBUG] Algorithm has changed to %s for load balancer rule %s, starting update", |
| algorithm, |
| name, |
| ) |
| |
| // Set the new Algorithm |
| p.SetAlgorithm(algorithm) |
| } |
| |
| _, err := cs.LoadBalancer.UpdateLoadBalancerRule(p) |
| if err != nil { |
| return fmt.Errorf( |
| "Error updating load balancer rule %s", name) |
| } |
| } |
| |
| if d.HasChange("certificate_id") { |
| p := cs.LoadBalancer.NewRemoveCertFromLoadBalancerParams(d.Id()) |
| if _, err := cs.LoadBalancer.RemoveCertFromLoadBalancer(p); err != nil { |
| return err |
| } |
| |
| _, certificateID := d.GetChange("certificate_id") |
| cp := cs.LoadBalancer.NewAssignCertToLoadBalancerParams(certificateID.(string), d.Id()) |
| if _, err := cs.LoadBalancer.AssignCertToLoadBalancer(cp); err != nil { |
| return err |
| } |
| } |
| |
| if d.HasChange("member_ids") { |
| o, n := d.GetChange("member_ids") |
| ombs, nmbs := o.(*schema.Set), n.(*schema.Set) |
| |
| setToStringList := func(s *schema.Set) []string { |
| l := make([]string, s.Len()) |
| for i, v := range s.List() { |
| l[i] = v.(string) |
| } |
| return l |
| } |
| |
| membersToAdd := setToStringList(nmbs.Difference(ombs)) |
| membersToRemove := setToStringList(ombs.Difference(nmbs)) |
| |
| log.Printf("[DEBUG] Members to add: %v, remove: %v", membersToAdd, membersToRemove) |
| |
| if len(membersToAdd) > 0 { |
| p := cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(d.Id()) |
| p.SetVirtualmachineids(membersToAdd) |
| if _, err := cs.LoadBalancer.AssignToLoadBalancerRule(p); err != nil { |
| return err |
| } |
| } |
| |
| if len(membersToRemove) > 0 { |
| p := cs.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(d.Id()) |
| p.SetVirtualmachineids(membersToRemove) |
| if _, err := cs.LoadBalancer.RemoveFromLoadBalancerRule(p); err != nil { |
| return err |
| } |
| } |
| } |
| |
| return resourceCloudStackLoadBalancerRuleRead(d, meta) |
| } |
| |
| func resourceCloudStackLoadBalancerRuleDelete(d *schema.ResourceData, meta interface{}) error { |
| cs := meta.(*cloudstack.CloudStackClient) |
| |
| // Create a new parameter struct |
| p := cs.LoadBalancer.NewDeleteLoadBalancerRuleParams(d.Id()) |
| |
| log.Printf("[INFO] Deleting load balancer rule: %s", d.Get("name").(string)) |
| if _, err := cs.LoadBalancer.DeleteLoadBalancerRule(p); err != nil { |
| // This is a very poor way to be told the ID does no longer exist :( |
| if !strings.Contains(err.Error(), fmt.Sprintf( |
| "Invalid parameter id value=%s due to incorrect long value format, "+ |
| "or entity does not exist", d.Id())) { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func verifyLoadBalancerRule(d *schema.ResourceData) error { |
| if protocol, ok := d.GetOk("protocol"); ok { |
| protocol := protocol.(string) |
| |
| switch protocol { |
| case "tcp", "udp", "tcp-proxy": |
| // These are supported |
| default: |
| return fmt.Errorf( |
| "%q is not a valid protocol. Valid options are 'tcp', 'udp' of 'tcp-proxy'", protocol) |
| } |
| } |
| |
| return nil |
| } |